diff options
101 files changed, 3630 insertions, 2414 deletions
| diff --git a/Android.mk b/Android.mk index ac72dc696636..77a4d83cc33e 100644 --- a/Android.mk +++ b/Android.mk @@ -502,8 +502,8 @@ LOCAL_SRC_FILES += \  	telecomm/java/com/android/internal/telecom/IInCallService.aidl \  	telecomm/java/com/android/internal/telecom/ITelecomService.aidl \  	telecomm/java/com/android/internal/telecom/RemoteServiceCallback.aidl \ -        telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl \ -	telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl \ +	telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl \ +	telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl \  	telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl \          telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl \  	telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl \ diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index b963561dfd3c..0536ff155513 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1138,7 +1138,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {          mMonitor = new MyPackageMonitor();          mMonitor.register(context, null, UserHandle.ALL, true);          getWallpaperDir(UserHandle.USER_SYSTEM).mkdirs(); + +        // Initialize state from the persistent store, then guarantee that the +        // WallpaperData for the system imagery is instantiated & active, creating +        // it from defaults if necessary.          loadSettingsLocked(UserHandle.USER_SYSTEM, false); +        getWallpaperSafeLocked(UserHandle.USER_SYSTEM, FLAG_SYSTEM); +          mColorsChangedListeners = new SparseArray<>();      } @@ -1627,13 +1633,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {                      (which == FLAG_LOCK) ? mLockWallpaperMap : mWallpaperMap;              WallpaperData wallpaper = whichSet.get(wallpaperUserId);              if (wallpaper == null) { -                // common case, this is the first lookup post-boot of the system or -                // unified lock, so we bring up the saved state lazily now and recheck. -                loadSettingsLocked(wallpaperUserId, false); -                wallpaper = whichSet.get(wallpaperUserId); -                if (wallpaper == null) { -                    return null; -                } +                // There is no established wallpaper imagery of this type (expected +                // only for lock wallpapers; a system WallpaperData is established at +                // user switch) +                return null;              }              try {                  if (outParams != null) { @@ -1658,7 +1661,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {      @Override      public WallpaperInfo getWallpaperInfo(int userId) {          userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), -                Binder.getCallingUid(), userId, false, true, "getWallpaperIdForUser", null); +                Binder.getCallingUid(), userId, false, true, "getWallpaperInfo", null);          synchronized (mLock) {              WallpaperData wallpaper = mWallpaperMap.get(userId);              if (wallpaper != null && wallpaper.connection != null) { @@ -2260,7 +2263,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {      private void writeWallpaperAttributes(XmlSerializer out, String tag, WallpaperData wallpaper)              throws IllegalArgumentException, IllegalStateException, IOException {          if (DEBUG) { -            Slog.v(TAG, "writeWallpaperAttributes"); +            Slog.v(TAG, "writeWallpaperAttributes id=" + wallpaper.wallpaperId);          }          out.startTag(null, tag);          out.attribute(null, "id", Integer.toString(wallpaper.wallpaperId)); @@ -2359,6 +2362,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {       * the event yet.  We use this safe method when we don't care about this ordering and just       * want to update the data.  The data is going to be applied when the user switch observer       * is eventually executed. +     * +     * Important: this method loads settings to initialize the given user's wallpaper data if +     * there is no current in-memory state.       */      private WallpaperData getWallpaperSafeLocked(int userId, int which) {          // We're setting either just system (work with the system wallpaper), @@ -2397,8 +2403,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {      }      private void loadSettingsLocked(int userId, boolean keepDimensionHints) { -        if (DEBUG) Slog.v(TAG, "loadSettingsLocked"); -          JournaledFile journal = makeJournaledFile(userId);          FileInputStream stream = null;          File file = journal.chooseForRead(); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 5cd4fc480275..37695cba7b9d 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3020,7 +3020,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo                  final boolean foundTargetWs =                          (w.mAppToken != null && w.mAppToken.token == appToken)                                  || (mScreenshotApplicationState.appWin != null && wallpaperOnly); -                if (foundTargetWs && winAnim.getShown()) { +                if (foundTargetWs && winAnim.getShown() && winAnim.mLastAlpha > 0f) {                      mScreenshotApplicationState.screenshotReady = true;                  } diff --git a/telephony/java/android/telephony/MbmsDownloadManager.java b/telephony/java/android/telephony/MbmsDownloadSession.java index f0665396f9df..01ed69084d1c 100644 --- a/telephony/java/android/telephony/MbmsDownloadManager.java +++ b/telephony/java/android/telephony/MbmsDownloadSession.java @@ -34,11 +34,11 @@ import android.os.RemoteException;  import android.telephony.mbms.DownloadStateCallback;  import android.telephony.mbms.FileInfo;  import android.telephony.mbms.DownloadRequest; -import android.telephony.mbms.InternalDownloadManagerCallback; +import android.telephony.mbms.InternalDownloadSessionCallback;  import android.telephony.mbms.InternalDownloadStateCallback; -import android.telephony.mbms.MbmsDownloadManagerCallback; +import android.telephony.mbms.MbmsDownloadSessionCallback;  import android.telephony.mbms.MbmsDownloadReceiver; -import android.telephony.mbms.MbmsException; +import android.telephony.mbms.MbmsErrors;  import android.telephony.mbms.MbmsTempFileProvider;  import android.telephony.mbms.MbmsUtils;  import android.telephony.mbms.vendor.IMbmsDownloadService; @@ -48,7 +48,10 @@ import java.io.File;  import java.io.IOException;  import java.lang.annotation.Retention;  import java.lang.annotation.RetentionPolicy; +import java.util.Collections; +import java.util.HashMap;  import java.util.List; +import java.util.Map;  import java.util.concurrent.atomic.AtomicBoolean;  import java.util.concurrent.atomic.AtomicReference; @@ -58,8 +61,8 @@ import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;   * This class provides functionality for file download over MBMS.   * @hide   */ -public class MbmsDownloadManager { -    private static final String LOG_TAG = MbmsDownloadManager.class.getSimpleName(); +public class MbmsDownloadSession implements AutoCloseable { +    private static final String LOG_TAG = MbmsDownloadSession.class.getSimpleName();      /**       * Service action which must be handled by the middleware implementing the MBMS file download @@ -95,16 +98,28 @@ public class MbmsDownloadManager {      /**       * {@link Uri} extra that Android will attach to the intent supplied via       * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)} -     * Indicates the location of the successfully -     * downloaded file. Will always be set to a non-null value if +     * Indicates the location of the successfully downloaded file within the temp file root set +     * via {@link #setTempFileRootDirectory(File)}. +     * While you may use this file in-place, it is highly encouraged that you move +     * this file to a different location after receiving the download completion intent, as this +     * file resides within the temp file directory. +     * +     * Will always be set to a non-null value if       * {@link #EXTRA_MBMS_DOWNLOAD_RESULT} is set to {@link #RESULT_SUCCESSFUL}.       */      public static final String EXTRA_MBMS_COMPLETED_FILE_URI =              "android.telephony.extra.MBMS_COMPLETED_FILE_URI";      /** +     * Extra containing the {@link DownloadRequest} for which the download result or file +     * descriptor request is for. Must not be null. +     */ +    public static final String EXTRA_MBMS_DOWNLOAD_REQUEST = +            "android.telephony.extra.MBMS_DOWNLOAD_REQUEST"; + +    /**       * The default directory name for all MBMS temp files. If you call -     * {@link #download(DownloadRequest, DownloadStateCallback, Handler)} without first calling +     * {@link #download(DownloadRequest)} without first calling       * {@link #setTempFileRootDirectory(File)}, this directory will be created for you under the       * path returned by {@link Context#getFilesDir()}.       */ @@ -173,98 +188,88 @@ public class MbmsDownloadManager {      private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {          @Override          public void binderDied() { -            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, "Received death notification"); +            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, "Received death notification");          }      };      private AtomicReference<IMbmsDownloadService> mService = new AtomicReference<>(null); -    private final InternalDownloadManagerCallback mInternalCallback; +    private final InternalDownloadSessionCallback mInternalCallback; +    private final Map<DownloadStateCallback, InternalDownloadStateCallback> +            mInternalDownloadCallbacks = new HashMap<>(); -    private MbmsDownloadManager(Context context, MbmsDownloadManagerCallback callback, +    private MbmsDownloadSession(Context context, MbmsDownloadSessionCallback callback,              int subscriptionId, Handler handler) {          mContext = context;          mSubscriptionId = subscriptionId;          if (handler == null) { -            handler = new Handler(Looper.myLooper()); +            handler = new Handler(Looper.getMainLooper());          } -        mInternalCallback = new InternalDownloadManagerCallback(callback, handler); +        mInternalCallback = new InternalDownloadSessionCallback(callback, handler);      }      /** -     * Create a new MbmsDownloadManager using the system default data subscription ID and default -     * {@link Handler} -     * See {@link #create(Context, MbmsDownloadManagerCallback, int, Handler)} +     * Create a new {@link MbmsDownloadSession} using the system default data subscription ID. +     * See {@link #create(Context, MbmsDownloadSessionCallback, int, Handler)}       */ -    public static MbmsDownloadManager create(Context context, -            MbmsDownloadManagerCallback callback) -            throws MbmsException { -        return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), null); -    } - -    /** -     * Create a new MbmsDownloadManager using the system default data subscription ID. -     * See {@link #create(Context, MbmsDownloadManagerCallback, int, Handler)} -     */ -    public static MbmsDownloadManager create(Context context, -            MbmsDownloadManagerCallback callback, Handler handler) -            throws MbmsException { +    public static MbmsDownloadSession create(@NonNull Context context, +            @NonNull MbmsDownloadSessionCallback callback, @NonNull Handler handler) {          return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), handler);      }      /** -     * Create a new MbmsDownloadManager using the default {@link Handler} -     * See {@link #create(Context, MbmsDownloadManagerCallback, int, Handler)} -     */ -    public static MbmsDownloadManager create(Context context, -            MbmsDownloadManagerCallback callback, int subscriptionId) -            throws MbmsException { -        return create(context, callback, subscriptionId, null); -    } - -    /**       * Create a new MbmsDownloadManager using the given subscription ID.       *       * Note that this call will bind a remote service and that may take a bit. The instance of -     * {@link MbmsDownloadManager} that is returned will not be ready for use until -     * {@link MbmsDownloadManagerCallback#onMiddlewareReady()} is called on the provided callback. -     * If you attempt to use the manager before it is ready, a {@link MbmsException} will be thrown. +     * {@link MbmsDownloadSession} that is returned will not be ready for use until +     * {@link MbmsDownloadSessionCallback#onMiddlewareReady()} is called on the provided callback. +     * If you attempt to use the instance before it is ready, an {@link IllegalStateException} +     * will be thrown or an error will be delivered through +     * {@link MbmsDownloadSessionCallback#onError(int, String)}.       * -     * This also may throw an {@link IllegalArgumentException} or an {@link IllegalStateException}. +     * This also may throw an {@link IllegalArgumentException}.       * -     * You may only have one instance of {@link MbmsDownloadManager} per UID. If you call this -     * method while there is an active instance of {@link MbmsDownloadManager} in your process -     * (in other words, one that has not had {@link #dispose()} called on it), this method will -     * throw an {@link MbmsException}. If you call this method in a different process +     * You may only have one instance of {@link MbmsDownloadSession} per UID. If you call this +     * method while there is an active instance of {@link MbmsDownloadSession} in your process +     * (in other words, one that has not had {@link #close()} called on it), this method will +     * throw an {@link IllegalStateException}. If you call this method in a different process       * running under the same UID, an error will be indicated via -     * {@link MbmsDownloadManagerCallback#onError(int, String)}. +     * {@link MbmsDownloadSessionCallback#onError(int, String)}.       *       * Note that initialization may fail asynchronously. If you wish to try again after you -     * receive such an asynchronous error, you must call dispose() on the instance of -     * {@link MbmsDownloadManager} that you received before calling this method again. +     * receive such an asynchronous error, you must call {@link #close()} on the instance of +     * {@link MbmsDownloadSession} that you received before calling this method again.       *       * @param context The instance of {@link Context} to use -     * @param listener A callback to get asynchronous error messages and file service updates. +     * @param callback A callback to get asynchronous error messages and file service updates.       * @param subscriptionId The data subscription ID to use +     * @param handler The {@link Handler} on which callbacks should be enqueued. +     * @return A new instance of {@link MbmsDownloadSession}, or null if an error occurred during +     * setup.       */ -    public static MbmsDownloadManager create(Context context, -            MbmsDownloadManagerCallback listener, int subscriptionId, Handler handler) -            throws MbmsException { +    public static @Nullable MbmsDownloadSession create(@NonNull Context context, +            final @NonNull MbmsDownloadSessionCallback callback, +            int subscriptionId, @NonNull Handler handler) {          if (!sIsInitialized.compareAndSet(false, true)) { -            throw new MbmsException(MbmsException.InitializationErrors.ERROR_DUPLICATE_INITIALIZE); +            throw new IllegalStateException("Cannot have two active instances");          } -        MbmsDownloadManager mdm = -                new MbmsDownloadManager(context, listener, subscriptionId, handler); -        try { -            mdm.bindAndInitialize(); -        } catch (MbmsException e) { +        MbmsDownloadSession session = +                new MbmsDownloadSession(context, callback, subscriptionId, handler); +        final int result = session.bindAndInitialize(); +        if (result != MbmsErrors.SUCCESS) {              sIsInitialized.set(false); -            throw e; -        } -        return mdm; +            handler.post(new Runnable() { +                @Override +                public void run() { +                    callback.onError(result, null); +                } +            }); +            return null; +        } +        return session;      } -    private void bindAndInitialize() throws MbmsException { -        MbmsUtils.startBinding(mContext, MBMS_DOWNLOAD_SERVICE_ACTION, +    private int bindAndInitialize() { +        return MbmsUtils.startBinding(mContext, MBMS_DOWNLOAD_SERVICE_ACTION,                  new ServiceConnection() {                      @Override                      public void onServiceConnected(ComponentName name, IBinder service) { @@ -280,12 +285,12 @@ public class MbmsDownloadManager {                          } catch (RuntimeException e) {                              Log.e(LOG_TAG, "Runtime exception during initialization");                              sendErrorToApp( -                                    MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE, +                                    MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,                                      e.toString());                              sIsInitialized.set(false);                              return;                          } -                        if (result != MbmsException.SUCCESS) { +                        if (result != MbmsErrors.SUCCESS) {                              sendErrorToApp(result, "Error returned during initialization");                              sIsInitialized.set(false);                              return; @@ -293,7 +298,7 @@ public class MbmsDownloadManager {                          try {                              downloadService.asBinder().linkToDeath(mDeathRecipient, 0);                          } catch (RemoteException e) { -                            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, +                            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST,                                      "Middleware lost during initialization");                              sIsInitialized.set(false);                              return; @@ -313,39 +318,35 @@ public class MbmsDownloadManager {       * An inspection API to retrieve the list of available       * {@link android.telephony.mbms.FileServiceInfo}s currently being advertised.       * The results are returned asynchronously via a call to -     * {@link MbmsDownloadManagerCallback#onFileServicesUpdated(List)} +     * {@link MbmsDownloadSessionCallback#onFileServicesUpdated(List)}       * -     * The serviceClasses argument lets the app filter on types of programming and is opaque data -     * negotiated beforehand between the app and the carrier. +     * Asynchronous error codes via the {@link MbmsDownloadSessionCallback#onError(int, String)} +     * callback may include any of the errors that are not specific to the streaming use-case.       * -     * This may throw an {@link MbmsException} containing one of the following errors: -     * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND} -     * {@link MbmsException#ERROR_MIDDLEWARE_LOST} -     * -     * Asynchronous error codes via the {@link MbmsDownloadManagerCallback#onError(int, String)} -     * callback can include any of the errors except: -     * {@link MbmsException.StreamingErrors#ERROR_UNABLE_TO_START_SERVICE} +     * May throw an {@link IllegalStateException} or {@link IllegalArgumentException}.       *       * @param classList A list of service classes which the app wishes to receive -     *                  {@link MbmsDownloadManagerCallback#onFileServicesUpdated(List)} callbacks +     *                  {@link MbmsDownloadSessionCallback#onFileServicesUpdated(List)} callbacks       *                  about. Subsequent calls to this method will replace this list of service       *                  classes (i.e. the middleware will no longer send updates for services       *                  matching classes only in the old list). +     *                  Values in this list should be negotiated with the wireless carrier prior +     *                  to using this API.       */ -    public void getFileServices(List<String> classList) throws MbmsException { +    public void requestUpdateFileServices(@NonNull List<String> classList) {          IMbmsDownloadService downloadService = mService.get();          if (downloadService == null) { -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); +            throw new IllegalStateException("Middleware not yet bound");          }          try { -            int returnCode = downloadService.getFileServices(mSubscriptionId, classList); -            if (returnCode != MbmsException.SUCCESS) { -                throw new MbmsException(returnCode); +            int returnCode = downloadService.requestUpdateFileServices(mSubscriptionId, classList); +            if (returnCode != MbmsErrors.SUCCESS) { +                sendErrorToApp(returnCode, null);              }          } catch (RemoteException e) {              Log.w(LOG_TAG, "Remote process died");              mService.set(null); -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); +            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);          }      } @@ -357,31 +358,32 @@ public class MbmsDownloadManager {       * local instance of {@link android.content.SharedPreferences} and by the middleware.       *       * If this method is not called at least once before calling -     * {@link #download(DownloadRequest, DownloadStateCallback, Handler)}, the framework +     * {@link #download(DownloadRequest)}, the framework       * will default to a directory formed by the concatenation of the app's files directory and -     * {@link MbmsDownloadManager#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY}. +     * {@link MbmsDownloadSession#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY}.       *       * Before calling this method, the app must cancel all of its pending       * {@link DownloadRequest}s via {@link #cancelDownload(DownloadRequest)}. If this is not done, -     * an {@link MbmsException} will be thrown with code -     * {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} unless the +     * you will receive an asynchronous error with code +     * {@link MbmsErrors.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} unless the       * provided directory is the same as what has been previously configured.       *       * The {@link File} supplied as a root temp file directory must already exist. If not, an -     * {@link IllegalArgumentException} will be thrown. +     * {@link IllegalArgumentException} will be thrown. In addition, as an additional sanity +     * check, an {@link IllegalArgumentException} will be thrown if you attempt to set the temp +     * file root directory to one of your data roots (the value of {@link Context#getDataDir()}, +     * {@link Context#getFilesDir()}, or {@link Context#getCacheDir()}).       * @param tempFileRootDirectory A directory to place temp files in.       */ -    public void setTempFileRootDirectory(@NonNull File tempFileRootDirectory) -            throws MbmsException { +    public void setTempFileRootDirectory(@NonNull File tempFileRootDirectory) {          IMbmsDownloadService downloadService = mService.get();          if (downloadService == null) { -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); +            throw new IllegalStateException("Middleware not yet bound");          } -        if (!tempFileRootDirectory.exists()) { -            throw new IllegalArgumentException("Provided directory does not exist"); -        } -        if (!tempFileRootDirectory.isDirectory()) { -            throw new IllegalArgumentException("Provided File is not a directory"); +        try { +            validateTempFileRootSanity(tempFileRootDirectory); +        } catch (IOException e) { +            throw new IllegalStateException("Got IOException checking directory sanity");          }          String filePath;          try { @@ -392,12 +394,13 @@ public class MbmsDownloadManager {          try {              int result = downloadService.setTempFileRootDirectory(mSubscriptionId, filePath); -            if (result != MbmsException.SUCCESS) { -                throw new MbmsException(result); +            if (result != MbmsErrors.SUCCESS) { +                sendErrorToApp(result, null);              }          } catch (RemoteException e) {              mService.set(null); -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); +            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); +            return;          }          SharedPreferences prefs = mContext.getSharedPreferences( @@ -405,10 +408,28 @@ public class MbmsDownloadManager {          prefs.edit().putString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, filePath).apply();      } +    private void validateTempFileRootSanity(File tempFileRootDirectory) throws IOException { +        if (!tempFileRootDirectory.exists()) { +            throw new IllegalArgumentException("Provided directory does not exist"); +        } +        if (!tempFileRootDirectory.isDirectory()) { +            throw new IllegalArgumentException("Provided File is not a directory"); +        } +        String canonicalTempFilePath = tempFileRootDirectory.getCanonicalPath(); +        if (mContext.getDataDir().getCanonicalPath().equals(canonicalTempFilePath)) { +            throw new IllegalArgumentException("Temp file root cannot be your data dir"); +        } +        if (mContext.getCacheDir().getCanonicalPath().equals(canonicalTempFilePath)) { +            throw new IllegalArgumentException("Temp file root cannot be your cache dir"); +        } +        if (mContext.getFilesDir().getCanonicalPath().equals(canonicalTempFilePath)) { +            throw new IllegalArgumentException("Temp file root cannot be your files dir"); +        } +    }      /**       * Retrieves the currently configured temp file root directory. Returns the file that was       * configured via {@link #setTempFileRootDirectory(File)} or the default directory -     * {@link #download(DownloadRequest, DownloadStateCallback, Handler)} was called without ever +     * {@link #download(DownloadRequest)} was called without ever       * setting the temp file root. If neither method has been called since the last time the app's       * shared preferences were reset, returns {@code null}.       * @@ -426,31 +447,24 @@ public class MbmsDownloadManager {      }      /** -     * Requests a download of a file that is available via multicast. +     * Requests the download of a file or set of files that the carrier has indicated to be +     * available.       *       * May throw an {@link IllegalArgumentException}       *       * If {@link #setTempFileRootDirectory(File)} has not called after the app has been installed,       * this method will create a directory at the default location defined at -     * {@link MbmsDownloadManager#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY} and store that as the temp +     * {@link MbmsDownloadSession#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY} and store that as the temp       * file root directory.       * -     * Asynchronous errors through the listener include any of the errors -     * -     * @param request The request that specifies what should be downloaded -     * @param stateCallback Optional listener that will be provided progress updates -     *                         if the app is running. If {@code null}, no callbacks will be -     *                         provided. -     * @param handler A handler that calls to {@code stateCallback} should be called on. If -     *                null, defaults to the handler provided via -     *                {@link #create(Context, MbmsDownloadManagerCallback, int, Handler)} +     * Asynchronous errors through the callback may include any error not specific to the +     * streaming use-case. +     * @param request The request that specifies what should be downloaded.       */ -    public void download(DownloadRequest request, @Nullable DownloadStateCallback stateCallback, -            Handler handler) -            throws MbmsException { +    public void download(@NonNull DownloadRequest request) {          IMbmsDownloadService downloadService = mService.get();          if (downloadService == null) { -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); +            throw new IllegalStateException("Middleware not yet bound");          }          // Check to see whether the app's set a temp root dir yet, and set it if not. @@ -462,67 +476,151 @@ public class MbmsDownloadManager {              tempRootDirectory.mkdirs();              setTempFileRootDirectory(tempRootDirectory);          } -        InternalDownloadStateCallback internalCallback = null; -        if (stateCallback != null) { -            internalCallback = new InternalDownloadStateCallback(stateCallback, -                    handler == null ? mInternalCallback.getHandler() : handler); -        } -        checkValidDownloadDestination(request);          writeDownloadRequestToken(request);          try { -            downloadService.download(request, internalCallback); +            downloadService.download(request);          } catch (RemoteException e) {              mService.set(null); -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); +            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);          }      }      /**       * Returns a list of pending {@link DownloadRequest}s that originated from this application.       * A pending request is one that was issued via -     * {@link #download(DownloadRequest, DownloadStateCallback, Handler)} but not cancelled through +     * {@link #download(DownloadRequest)} but not cancelled through       * {@link #cancelDownload(DownloadRequest)}.       * @return A list, possibly empty, of {@link DownloadRequest}s       */ -    public @NonNull List<DownloadRequest> listPendingDownloads() throws MbmsException { +    public @NonNull List<DownloadRequest> listPendingDownloads() {          IMbmsDownloadService downloadService = mService.get();          if (downloadService == null) { -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); +            throw new IllegalStateException("Middleware not yet bound");          }          try {              return downloadService.listPendingDownloads(mSubscriptionId);          } catch (RemoteException e) {              mService.set(null); -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); +            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); +            return Collections.emptyList(); +        } +    } + +    /** +     * Registers a callback for a {@link DownloadRequest} previously requested via +     * {@link #download(DownloadRequest)}. This callback will only be called as long as both this +     * app and the middleware are both running -- if either one stops, no further calls on the +     * provided {@link DownloadStateCallback} will be enqueued. +     * +     * If the middleware is not aware of the specified download request, +     * this method will throw an {@link IllegalArgumentException}. +     * +     * @param request The {@link DownloadRequest} that you want updates on. +     * @param callback The callback that should be called when the middleware has information to +     *                 share on the download. +     * @param handler The {@link Handler} on which calls to {@code callback} should be enqueued on. +     */ +    public void registerStateCallback(@NonNull DownloadRequest request, +            @NonNull DownloadStateCallback callback, +            @NonNull Handler handler) { +        IMbmsDownloadService downloadService = mService.get(); +        if (downloadService == null) { +            throw new IllegalStateException("Middleware not yet bound"); +        } + +        InternalDownloadStateCallback internalCallback = +                new InternalDownloadStateCallback(callback, handler); + +        try { +            int result = downloadService.registerStateCallback(request, internalCallback); +            if (result != MbmsErrors.SUCCESS) { +                if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) { +                    throw new IllegalArgumentException("Unknown download request."); +                } +                sendErrorToApp(result, null); +                return; +            } +        } catch (RemoteException e) { +            mService.set(null); +            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); +            return; +        } +        mInternalDownloadCallbacks.put(callback, internalCallback); +    } + +    /** +     * Un-register a callback previously registered via +     * {@link #registerStateCallback(DownloadRequest, DownloadStateCallback, Handler)}. After +     * this method is called, no further callbacks will be enqueued on the {@link Handler} +     * provided upon registration, even if this method throws an exception. +     * +     * If the middleware is not aware of the specified download request, +     * this method will throw an {@link IllegalArgumentException}. +     * +     * @param request The {@link DownloadRequest} provided during registration +     * @param callback The callback provided during registration. +     */ +    public void unregisterStateCallback(@NonNull DownloadRequest request, +            @NonNull DownloadStateCallback callback) { +        try { +            IMbmsDownloadService downloadService = mService.get(); +            if (downloadService == null) { +                throw new IllegalStateException("Middleware not yet bound"); +            } + +            InternalDownloadStateCallback internalCallback = +                    mInternalDownloadCallbacks.get(callback); + +            try { +                int result = downloadService.unregisterStateCallback(request, internalCallback); +                if (result != MbmsErrors.SUCCESS) { +                    if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) { +                        throw new IllegalArgumentException("Unknown download request."); +                    } +                    sendErrorToApp(result, null); +                } +            } catch (RemoteException e) { +                mService.set(null); +                sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); +            } +        } finally { +            InternalDownloadStateCallback internalCallback = +                    mInternalDownloadCallbacks.remove(callback); +            if (internalCallback != null) { +                internalCallback.stop(); +            }          }      }      /**       * Attempts to cancel the specified {@link DownloadRequest}.       * -     * If the middleware is not aware of the specified download request, an MbmsException will be -     * thrown with error code {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}. +     * If the middleware is not aware of the specified download request, +     * this method will throw an {@link IllegalArgumentException}.       * -     * If this method returns without throwing an exception, you may assume that cancellation -     * was successful.       * @param downloadRequest The download request that you wish to cancel.       */ -    public void cancelDownload(DownloadRequest downloadRequest) throws MbmsException { +    public void cancelDownload(@NonNull DownloadRequest downloadRequest) {          IMbmsDownloadService downloadService = mService.get();          if (downloadService == null) { -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); +            throw new IllegalStateException("Middleware not yet bound");          }          try {              int result = downloadService.cancelDownload(downloadRequest); -            if (result != MbmsException.SUCCESS) { -                throw new MbmsException(result); +            if (result != MbmsErrors.SUCCESS) { +                if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) { +                    throw new IllegalArgumentException("Unknown download request."); +                } +                sendErrorToApp(result, null); +                return;              }          } catch (RemoteException e) {              mService.set(null); -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); +            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); +            return;          }          deleteDownloadRequestToken(downloadRequest);      } @@ -530,7 +628,7 @@ public class MbmsDownloadManager {      /**       * Gets information about the status of a file pending download.       * -     * If the middleware has not yet been properly initialized or if it has no records of the +     * If there was a problem communicating with the middleware or if it has no records of the       * file indicated by {@code fileInfo} being associated with {@code downloadRequest},       * {@link #STATUS_UNKNOWN} will be returned.       * @@ -539,18 +637,18 @@ public class MbmsDownloadManager {       * @return The status of the download.       */      @DownloadStatus -    public int getDownloadStatus(DownloadRequest downloadRequest, FileInfo fileInfo) -            throws MbmsException { +    public int getDownloadStatus(DownloadRequest downloadRequest, FileInfo fileInfo) {          IMbmsDownloadService downloadService = mService.get();          if (downloadService == null) { -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); +            throw new IllegalStateException("Middleware not yet bound");          }          try {              return downloadService.getDownloadStatus(downloadRequest, fileInfo);          } catch (RemoteException e) {              mService.set(null); -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); +            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); +            return STATUS_UNKNOWN;          }      } @@ -566,30 +664,50 @@ public class MbmsDownloadManager {       * when available.       * This will not interrupt in-progress downloads.       * -     * If the middleware is not aware of the specified download request, an MbmsException will be -     * thrown with error code {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}. +     * This is distinct from cancelling and re-issuing the download request -- if you cancel and +     * re-issue, the middleware will not clear its cache of download state information. +     * +     * If the middleware is not aware of the specified download request, an +     * {@link IllegalArgumentException} will be thrown.       * -     * May throw a {@link MbmsException} with error code       * @param downloadRequest The request to re-download files for.       */ -    public void resetDownloadKnowledge(DownloadRequest downloadRequest) throws MbmsException { +    public void resetDownloadKnowledge(DownloadRequest downloadRequest) {          IMbmsDownloadService downloadService = mService.get();          if (downloadService == null) { -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); +            throw new IllegalStateException("Middleware not yet bound");          }          try {              int result = downloadService.resetDownloadKnowledge(downloadRequest); -            if (result != MbmsException.SUCCESS) { -                throw new MbmsException(result); +            if (result != MbmsErrors.SUCCESS) { +                if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) { +                    throw new IllegalArgumentException("Unknown download request."); +                } +                sendErrorToApp(result, null);              }          } catch (RemoteException e) {              mService.set(null); -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); +            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);          }      } -    public void dispose() { +    /** +     * Terminates this instance. +     * +     * After this method returns, +     * no further callbacks originating from the middleware will be enqueued on the provided +     * instance of {@link MbmsDownloadSessionCallback}, but callbacks that have already been +     * enqueued will still be delivered. +     * +     * It is safe to call {@link #create(Context, MbmsDownloadSessionCallback, int, Handler)} to +     * obtain another instance of {@link MbmsDownloadSession} immediately after this method +     * returns. +     * +     * May throw an {@link IllegalStateException} +     */ +    @Override +    public void close() {          try {              IMbmsDownloadService downloadService = mService.get();              if (downloadService == null) { @@ -603,6 +721,7 @@ public class MbmsDownloadManager {          } finally {              mService.set(null);              sIsInitialized.set(false); +            mInternalCallback.stop();          }      } @@ -645,35 +764,9 @@ public class MbmsDownloadManager {          return new File(tempFileLocation, downloadTokenFileName);      } -    /** -     * Verifies the following: -     * If a request is multi-part, -     *     1. Destination Uri must exist and be a directory -     *     2. Directory specified must contain no files. -     * Otherwise -     *     1. The file specified by the destination Uri must not exist. -     */ -    private void checkValidDownloadDestination(DownloadRequest request) { -        File toFile = new File(request.getDestinationUri().getSchemeSpecificPart()); -        if (request.isMultipartDownload()) { -            if (!toFile.isDirectory()) { -                throw new IllegalArgumentException("Multipart download must specify valid " + -                        "destination directory."); -            } -            if (toFile.listFiles().length > 0) { -                throw new IllegalArgumentException("Destination directory must be clear of all " + -                        "files."); -            } -        } else { -            if (toFile.exists()) { -                throw new IllegalArgumentException("Destination file must not exist."); -            } -        } -    } -      private void sendErrorToApp(int errorCode, String message) {          try { -            mInternalCallback.error(errorCode, message); +            mInternalCallback.onError(errorCode, message);          } catch (RemoteException e) {              // Ignore, should not happen locally.          } diff --git a/telephony/java/android/telephony/MbmsStreamingManager.java b/telephony/java/android/telephony/MbmsStreamingSession.java index 7a6631a3d90c..efb6055e1d6a 100644 --- a/telephony/java/android/telephony/MbmsStreamingManager.java +++ b/telephony/java/android/telephony/MbmsStreamingSession.java @@ -16,6 +16,8 @@  package android.telephony; +import android.annotation.NonNull; +import android.annotation.Nullable;  import android.annotation.SdkConstant;  import android.annotation.SystemApi;  import android.content.ComponentName; @@ -25,18 +27,20 @@ import android.os.Handler;  import android.os.IBinder;  import android.os.Looper;  import android.os.RemoteException; -import android.telephony.mbms.InternalStreamingManagerCallback; +import android.telephony.mbms.InternalStreamingSessionCallback;  import android.telephony.mbms.InternalStreamingServiceCallback; -import android.telephony.mbms.MbmsException; -import android.telephony.mbms.MbmsStreamingManagerCallback; +import android.telephony.mbms.MbmsErrors; +import android.telephony.mbms.MbmsStreamingSessionCallback;  import android.telephony.mbms.MbmsUtils;  import android.telephony.mbms.StreamingService;  import android.telephony.mbms.StreamingServiceCallback;  import android.telephony.mbms.StreamingServiceInfo;  import android.telephony.mbms.vendor.IMbmsStreamingService; +import android.util.ArraySet;  import android.util.Log;  import java.util.List; +import java.util.Set;  import java.util.concurrent.atomic.AtomicBoolean;  import java.util.concurrent.atomic.AtomicReference; @@ -46,8 +50,8 @@ import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;   * This class provides functionality for streaming media over MBMS.   * @hide   */ -public class MbmsStreamingManager { -    private static final String LOG_TAG = "MbmsStreamingManager"; +public class MbmsStreamingSession implements AutoCloseable { +    private static final String LOG_TAG = "MbmsStreamingSession";      /**       * Service action which must be handled by the middleware implementing the MBMS streaming @@ -66,98 +70,98 @@ public class MbmsStreamingManager {          @Override          public void binderDied() {              sIsInitialized.set(false); -            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, "Received death notification"); +            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, "Received death notification");          }      }; -    private InternalStreamingManagerCallback mInternalCallback; +    private InternalStreamingSessionCallback mInternalCallback; +    private Set<StreamingService> mKnownActiveStreamingServices = new ArraySet<>();      private final Context mContext;      private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;      /** @hide */ -    private MbmsStreamingManager(Context context, MbmsStreamingManagerCallback callback, +    private MbmsStreamingSession(Context context, MbmsStreamingSessionCallback callback,                      int subscriptionId, Handler handler) {          mContext = context;          mSubscriptionId = subscriptionId;          if (handler == null) {              handler = new Handler(Looper.getMainLooper());          } -        mInternalCallback = new InternalStreamingManagerCallback(callback, handler); +        mInternalCallback = new InternalStreamingSessionCallback(callback, handler);      }      /** -     * Create a new MbmsStreamingManager using the given subscription ID. +     * Create a new {@link MbmsStreamingSession} using the given subscription ID.       *       * Note that this call will bind a remote service. You may not call this method on your app's -     * main thread. This may throw an {@link MbmsException}, indicating errors that may happen -     * during the initialization or binding process. +     * main thread.       * -     * -     * You may only have one instance of {@link MbmsStreamingManager} per UID. If you call this -     * method while there is an active instance of {@link MbmsStreamingManager} in your process -     * (in other words, one that has not had {@link #dispose()} called on it), this method will -     * throw an {@link MbmsException}. If you call this method in a different process +     * You may only have one instance of {@link MbmsStreamingSession} per UID. If you call this +     * method while there is an active instance of {@link MbmsStreamingSession} in your process +     * (in other words, one that has not had {@link #close()} called on it), this method will +     * throw an {@link IllegalStateException}. If you call this method in a different process       * running under the same UID, an error will be indicated via -     * {@link MbmsStreamingManagerCallback#onError(int, String)}. +     * {@link MbmsStreamingSessionCallback#onError(int, String)}.       *       * Note that initialization may fail asynchronously. If you wish to try again after you -     * receive such an asynchronous error, you must call dispose() on the instance of -     * {@link MbmsStreamingManager} that you received before calling this method again. +     * receive such an asynchronous error, you must call {@link #close()} on the instance of +     * {@link MbmsStreamingSession} that you received before calling this method again.       *       * @param context The {@link Context} to use.       * @param callback A callback object on which you wish to receive results of asynchronous       *                 operations.       * @param subscriptionId The subscription ID to use. -     * @param handler The handler you wish to receive callbacks on. If null, callbacks will be -     *                processed on the main looper (in other words, the looper returned from -     *                {@link Looper#getMainLooper()}). +     * @param handler The handler you wish to receive callbacks on. +     * @return An instance of {@link MbmsStreamingSession}, or null if an error occurred.       */ -    public static MbmsStreamingManager create(Context context, -            MbmsStreamingManagerCallback callback, int subscriptionId, Handler handler) -            throws MbmsException { +    public static @Nullable MbmsStreamingSession create(@NonNull Context context, +            final @NonNull MbmsStreamingSessionCallback callback, int subscriptionId, +            @NonNull Handler handler) {          if (!sIsInitialized.compareAndSet(false, true)) { -            throw new MbmsException(MbmsException.InitializationErrors.ERROR_DUPLICATE_INITIALIZE); +            throw new IllegalStateException("Cannot create two instances of MbmsStreamingSession");          } -        MbmsStreamingManager manager = new MbmsStreamingManager(context, callback, +        MbmsStreamingSession session = new MbmsStreamingSession(context, callback,                  subscriptionId, handler); -        try { -            manager.bindAndInitialize(); -        } catch (MbmsException e) { + +        final int result = session.bindAndInitialize(); +        if (result != MbmsErrors.SUCCESS) {              sIsInitialized.set(false); -            throw e; +            handler.post(new Runnable() { +                @Override +                public void run() { +                    callback.onError(result, null); +                } +            }); +            return null;          } -        return manager; +        return session;      }      /** -     * Create a new MbmsStreamingManager using the system default data subscription ID. -     * See {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}. +     * Create a new {@link MbmsStreamingSession} using the system default data subscription ID. +     * See {@link #create(Context, MbmsStreamingSessionCallback, int, Handler)}.       */ -    public static MbmsStreamingManager create(Context context, -            MbmsStreamingManagerCallback callback, Handler handler) -            throws MbmsException { +    public static MbmsStreamingSession create(@NonNull Context context, +            @NonNull MbmsStreamingSessionCallback callback, @NonNull Handler handler) {          return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), handler);      }      /** -     * Create a new MbmsStreamingManager using the system default data subscription ID and -     * default {@link Handler}. -     * See {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}. -     */ -    public static MbmsStreamingManager create(Context context, -            MbmsStreamingManagerCallback callback) -            throws MbmsException { -        return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), null); -    } - -    /** -     * Terminates this instance, ending calls to the registered listener.  Also terminates -     * any streaming services spawned from this instance. +     * Terminates this instance. Also terminates +     * any streaming services spawned from this instance as if +     * {@link StreamingService#stopStreaming()} had been called on them. After this method returns, +     * no further callbacks originating from the middleware will be enqueued on the provided +     * instance of {@link MbmsStreamingSessionCallback}, but callbacks that have already been +     * enqueued will still be delivered. +     * +     * It is safe to call {@link #create(Context, MbmsStreamingSessionCallback, int, Handler)} to +     * obtain another instance of {@link MbmsStreamingSession} immediately after this method +     * returns.       *       * May throw an {@link IllegalStateException}       */ -    public void dispose() { +    public void close() {          try {              IMbmsStreamingService streamingService = mService.get();              if (streamingService == null) { @@ -165,47 +169,49 @@ public class MbmsStreamingManager {                  return;              }              streamingService.dispose(mSubscriptionId); +            for (StreamingService s : mKnownActiveStreamingServices) { +                s.getCallback().stop(); +            } +            mKnownActiveStreamingServices.clear();          } catch (RemoteException e) {              // Ignore for now          } finally {              mService.set(null);              sIsInitialized.set(false); +            mInternalCallback.stop();          }      }      /**       * An inspection API to retrieve the list of streaming media currently be advertised. -     * The results are returned asynchronously through the previously registered callback. -     * serviceClasses lets the app filter on types of programming and is opaque data between -     * the app and the carrier. +     * The results are returned asynchronously via +     * {@link MbmsStreamingSessionCallback#onStreamingServicesUpdated(List)} on the callback +     * provided upon creation.       * -     * Multiple calls replace the list of serviceClasses of interest. +     * Multiple calls replace the list of service classes of interest.       * -     * This may throw an {@link MbmsException} containing any error in -     * {@link android.telephony.mbms.MbmsException.GeneralErrors}, -     * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}, or -     * {@link MbmsException#ERROR_MIDDLEWARE_LOST}. +     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}.       * -     * May also throw an unchecked {@link IllegalArgumentException} or an -     * {@link IllegalStateException} -     * -     * @param classList A list of streaming service classes that the app would like updates on. +     * @param serviceClassList A list of streaming service classes that the app would like updates +     *                         on. The exact names of these classes should be negotiated with the +     *                         wireless carrier separately.       */ -    public void getStreamingServices(List<String> classList) throws MbmsException { +    public void requestUpdateStreamingServices(List<String> serviceClassList) {          IMbmsStreamingService streamingService = mService.get();          if (streamingService == null) { -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); +            throw new IllegalStateException("Middleware not yet bound");          }          try { -            int returnCode = streamingService.getStreamingServices(mSubscriptionId, classList); -            if (returnCode != MbmsException.SUCCESS) { -                throw new MbmsException(returnCode); +            int returnCode = streamingService.requestUpdateStreamingServices( +                    mSubscriptionId, serviceClassList); +            if (returnCode != MbmsErrors.SUCCESS) { +                sendErrorToApp(returnCode, null);              }          } catch (RemoteException e) {              Log.w(LOG_TAG, "Remote process died");              mService.set(null);              sIsInitialized.set(false); -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); +            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);          }      } @@ -216,56 +222,57 @@ public class MbmsStreamingManager {       * reported via       * {@link android.telephony.mbms.StreamingServiceCallback#onStreamStateUpdated(int, int)}       * -     * May throw an -     * {@link MbmsException} containing any of the error codes in -     * {@link android.telephony.mbms.MbmsException.GeneralErrors}, -     * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}, or -     * {@link MbmsException#ERROR_MIDDLEWARE_LOST}. -     * -     * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException} +     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}       *       * Asynchronous errors through the callback include any of the errors in -     * {@link android.telephony.mbms.MbmsException.GeneralErrors} or -     * {@link android.telephony.mbms.MbmsException.StreamingErrors}. +     * {@link MbmsErrors.GeneralErrors} or +     * {@link MbmsErrors.StreamingErrors}.       *       * @param serviceInfo The information about the service to stream.       * @param callback A callback that'll be called when something about the stream changes. -     * @param handler A handler that calls to {@code callback} should be called on. If null, -     *                defaults to the handler provided via -     *                {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}. +     * @param handler A handler that calls to {@code callback} should be called on.       * @return An instance of {@link StreamingService} through which the stream can be controlled. +     *         May be {@code null} if an error occurred.       */ -    public StreamingService startStreaming(StreamingServiceInfo serviceInfo, -            StreamingServiceCallback callback, Handler handler) throws MbmsException { +    public @Nullable StreamingService startStreaming(StreamingServiceInfo serviceInfo, +            StreamingServiceCallback callback, @NonNull Handler handler) {          IMbmsStreamingService streamingService = mService.get();          if (streamingService == null) { -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); +            throw new IllegalStateException("Middleware not yet bound");          }          InternalStreamingServiceCallback serviceCallback = new InternalStreamingServiceCallback( -                callback, handler == null ? mInternalCallback.getHandler() : handler); +                callback, handler);          StreamingService serviceForApp = new StreamingService( -                mSubscriptionId, streamingService, serviceInfo, serviceCallback); +                mSubscriptionId, streamingService, this, serviceInfo, serviceCallback); +        mKnownActiveStreamingServices.add(serviceForApp);          try {              int returnCode = streamingService.startStreaming(                      mSubscriptionId, serviceInfo.getServiceId(), serviceCallback); -            if (returnCode != MbmsException.SUCCESS) { -                throw new MbmsException(returnCode); +            if (returnCode != MbmsErrors.SUCCESS) { +                sendErrorToApp(returnCode, null); +                return null;              }          } catch (RemoteException e) {              Log.w(LOG_TAG, "Remote process died");              mService.set(null);              sIsInitialized.set(false); -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); +            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); +            return null;          }          return serviceForApp;      } -    private void bindAndInitialize() throws MbmsException { -        MbmsUtils.startBinding(mContext, MBMS_STREAMING_SERVICE_ACTION, +    /** @hide */ +    public void onStreamingServiceStopped(StreamingService service) { +        mKnownActiveStreamingServices.remove(service); +    } + +    private int bindAndInitialize() { +        return MbmsUtils.startBinding(mContext, MBMS_STREAMING_SERVICE_ACTION,                  new ServiceConnection() {                      @Override                      public void onServiceConnected(ComponentName name, IBinder service) { @@ -278,19 +285,19 @@ public class MbmsStreamingManager {                          } catch (RemoteException e) {                              Log.e(LOG_TAG, "Service died before initialization");                              sendErrorToApp( -                                    MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE, +                                    MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,                                      e.toString());                              sIsInitialized.set(false);                              return;                          } catch (RuntimeException e) {                              Log.e(LOG_TAG, "Runtime exception during initialization");                              sendErrorToApp( -                                    MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE, +                                    MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,                                      e.toString());                              sIsInitialized.set(false);                              return;                          } -                        if (result != MbmsException.SUCCESS) { +                        if (result != MbmsErrors.SUCCESS) {                              sendErrorToApp(result, "Error returned during initialization");                              sIsInitialized.set(false);                              return; @@ -298,7 +305,7 @@ public class MbmsStreamingManager {                          try {                              streamingService.asBinder().linkToDeath(mDeathRecipient, 0);                          } catch (RemoteException e) { -                            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, +                            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST,                                      "Middleware lost during initialization");                              sIsInitialized.set(false);                              return; @@ -316,7 +323,7 @@ public class MbmsStreamingManager {      private void sendErrorToApp(int errorCode, String message) {          try { -            mInternalCallback.error(errorCode, message); +            mInternalCallback.onError(errorCode, message);          } catch (RemoteException e) {              // Ignore, should not happen locally.          } diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java index 8c3f71da5712..a5f256eab5dc 100644 --- a/telephony/java/android/telephony/mbms/DownloadRequest.java +++ b/telephony/java/android/telephony/mbms/DownloadRequest.java @@ -56,12 +56,10 @@ public final class DownloadRequest implements Parcelable {      /** @hide */      private static class OpaqueDataContainer implements Serializable { -        private final String destinationUri;          private final String appIntent;          private final int version; -        public OpaqueDataContainer(String destinationUri, String appIntent, int version) { -            this.destinationUri = destinationUri; +        public OpaqueDataContainer(String appIntent, int version) {              this.appIntent = appIntent;              this.version = version;          } @@ -70,7 +68,6 @@ public final class DownloadRequest implements Parcelable {      public static class Builder {          private String fileServiceId;          private Uri source; -        private Uri dest;          private int subscriptionId;          private String appIntent;          private int version = CURRENT_VERSION; @@ -105,21 +102,6 @@ public final class DownloadRequest implements Parcelable {          }          /** -         * Sets the destination URI for the download request to be built. The middleware should -         * not set this directly. -         * @param dest A URI obtained from {@link Uri#fromFile(File)}, denoting the requested -         *             final destination of the download. -         */ -        public Builder setDest(Uri dest) { -            if (dest.toString().length() > MAX_DESTINATION_URI_SIZE) { -                throw new IllegalArgumentException("Destination uri must not exceed length " + -                        MAX_DESTINATION_URI_SIZE); -            } -            this.dest = dest; -            return this; -        } - -        /**           * Set the subscription ID on which the file(s) should be downloaded.           * @param subscriptionId           */ @@ -160,7 +142,6 @@ public final class DownloadRequest implements Parcelable {                  OpaqueDataContainer dataContainer = (OpaqueDataContainer) stream.readObject();                  version = dataContainer.version;                  appIntent = dataContainer.appIntent; -                dest = Uri.parse(dataContainer.destinationUri);              } catch (IOException e) {                  // Really should never happen                  Log.e(LOG_TAG, "Got IOException trying to parse opaque data"); @@ -173,24 +154,21 @@ public final class DownloadRequest implements Parcelable {          }          public DownloadRequest build() { -            return new DownloadRequest(fileServiceId, source, dest, -                    subscriptionId, appIntent, version); +            return new DownloadRequest(fileServiceId, source, subscriptionId, appIntent, version);          }      }      private final String fileServiceId;      private final Uri sourceUri; -    private final Uri destinationUri;      private final int subscriptionId;      private final String serializedResultIntentForApp;      private final int version;      private DownloadRequest(String fileServiceId, -            Uri source, Uri dest, -            int sub, String appIntent, int version) { +            Uri source, int sub, +            String appIntent, int version) {          this.fileServiceId = fileServiceId;          sourceUri = source; -        destinationUri = dest;          subscriptionId = sub;          serializedResultIntentForApp = appIntent;          this.version = version; @@ -203,7 +181,6 @@ public final class DownloadRequest implements Parcelable {      private DownloadRequest(DownloadRequest dr) {          fileServiceId = dr.fileServiceId;          sourceUri = dr.sourceUri; -        destinationUri = dr.destinationUri;          subscriptionId = dr.subscriptionId;          serializedResultIntentForApp = dr.serializedResultIntentForApp;          version = dr.version; @@ -212,7 +189,6 @@ public final class DownloadRequest implements Parcelable {      private DownloadRequest(Parcel in) {          fileServiceId = in.readString();          sourceUri = in.readParcelable(getClass().getClassLoader()); -        destinationUri = in.readParcelable(getClass().getClassLoader());          subscriptionId = in.readInt();          serializedResultIntentForApp = in.readString();          version = in.readInt(); @@ -225,7 +201,6 @@ public final class DownloadRequest implements Parcelable {      public void writeToParcel(Parcel out, int flags) {          out.writeString(fileServiceId);          out.writeParcelable(sourceUri, flags); -        out.writeParcelable(destinationUri, flags);          out.writeInt(subscriptionId);          out.writeString(serializedResultIntentForApp);          out.writeInt(version); @@ -246,14 +221,6 @@ public final class DownloadRequest implements Parcelable {      }      /** -     * For use by the client app only. -     * @return The URI of the final destination of the download. -     */ -    public Uri getDestinationUri() { -        return destinationUri; -    } - -    /**       * @return The subscription ID on which to perform MBMS operations.       */      public int getSubscriptionId() { @@ -286,7 +253,7 @@ public final class DownloadRequest implements Parcelable {              ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();              ObjectOutputStream stream = new ObjectOutputStream(byteArrayOutputStream);              OpaqueDataContainer container = new OpaqueDataContainer( -                    destinationUri.toString(), serializedResultIntentForApp, version); +                    serializedResultIntentForApp, version);              stream.writeObject(container);              stream.flush();              return byteArrayOutputStream.toByteArray(); @@ -352,7 +319,6 @@ public final class DownloadRequest implements Parcelable {          if (version >= 1) {              // Hash the source URI, destination URI, and the app intent              digest.update(sourceUri.toString().getBytes(StandardCharsets.UTF_8)); -            digest.update(destinationUri.toString().getBytes(StandardCharsets.UTF_8));              digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));          }          // Add updates for future versions here @@ -373,13 +339,12 @@ public final class DownloadRequest implements Parcelable {                  version == request.version &&                  Objects.equals(fileServiceId, request.fileServiceId) &&                  Objects.equals(sourceUri, request.sourceUri) && -                Objects.equals(destinationUri, request.destinationUri) &&                  Objects.equals(serializedResultIntentForApp, request.serializedResultIntentForApp);      }      @Override      public int hashCode() { -        return Objects.hash(fileServiceId, sourceUri, destinationUri, +        return Objects.hash(fileServiceId, sourceUri,                  subscriptionId, serializedResultIntentForApp, version);      }  } diff --git a/telephony/java/android/telephony/mbms/DownloadStateCallback.java b/telephony/java/android/telephony/mbms/DownloadStateCallback.java index 9530641e0351..26d6879c425b 100644 --- a/telephony/java/android/telephony/mbms/DownloadStateCallback.java +++ b/telephony/java/android/telephony/mbms/DownloadStateCallback.java @@ -16,14 +16,12 @@  package android.telephony.mbms; -import android.os.Handler; -import android.telephony.MbmsDownloadManager; +import android.telephony.MbmsDownloadSession;  /**   * A optional listener class used by download clients to track progress. Apps should extend this   * class and pass an instance into - * {@link android.telephony.MbmsDownloadManager#download( - * DownloadRequest, DownloadStateCallback, Handler)} + * {@link MbmsDownloadSession#download(DownloadRequest)}   *   * This is optionally specified when requesting a download and will only be called while the app   * is running. @@ -59,7 +57,7 @@ public class DownloadStateCallback {       *   may not have been able to get a list of them in advance.       * @param state The current state of the download.       */ -    public void onStateChanged(DownloadRequest request, FileInfo fileInfo, -            @MbmsDownloadManager.DownloadStatus int state) { +    public void onStateUpdated(DownloadRequest request, FileInfo fileInfo, +            @MbmsDownloadSession.DownloadStatus int state) {      }  } diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.java b/telephony/java/android/telephony/mbms/FileServiceInfo.java index b2e80abde48c..51e20a3b617a 100644 --- a/telephony/java/android/telephony/mbms/FileServiceInfo.java +++ b/telephony/java/android/telephony/mbms/FileServiceInfo.java @@ -59,7 +59,7 @@ public final class FileServiceInfo extends ServiceInfo implements Parcelable {      FileServiceInfo(Parcel in) {          super(in);          files = new ArrayList<FileInfo>(); -        in.readList(files, null); +        in.readList(files, FileInfo.class.getClassLoader());      }      @Override diff --git a/telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl b/telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl index d62247be127d..cebc70d3e884 100755 --- a/telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl +++ b/telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl @@ -29,8 +29,9 @@ interface IDownloadStateCallback       * Gives progress callbacks for a given DownloadRequest.  Includes a FileInfo       * as the list of files may not have been known at request-time.       */ -    void progress(in DownloadRequest request, in FileInfo fileInfo, int currentDownloadSize, -            int fullDownloadSize, int currentDecodedSize, int fullDecodedSize); +    void onProgressUpdated(in DownloadRequest request, in FileInfo fileInfo, +            int currentDownloadSize, int fullDownloadSize, +            int currentDecodedSize, int fullDecodedSize); -    void state(in DownloadRequest request, in FileInfo fileInfo, int state); +    void onStateUpdated(in DownloadRequest request, in FileInfo fileInfo, int state);  } diff --git a/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl b/telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl index ac2f20243896..0d813a7ceea0 100755 --- a/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl +++ b/telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl @@ -24,11 +24,11 @@ import java.util.List;   * The interface the clients top-level file download listener will satisfy.   * @hide   */ -oneway interface IMbmsDownloadManagerCallback +oneway interface IMbmsDownloadSessionCallback  { -    void error(int errorCode, String message); +    void onError(int errorCode, String message); -    void fileServicesUpdated(in List<FileServiceInfo> services); +    void onFileServicesUpdated(in List<FileServiceInfo> services); -    void middlewareReady(); +    void onMiddlewareReady();  } diff --git a/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl b/telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl index 007aee7cf3f2..0bf0ebc484ea 100755 --- a/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl +++ b/telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl @@ -24,11 +24,11 @@ import java.util.List;   * The interface the clients top-level streaming listener will satisfy.   * @hide   */ -oneway interface IMbmsStreamingManagerCallback +oneway interface IMbmsStreamingSessionCallback  { -    void error(int errorCode, String message); +    void onError(int errorCode, String message); -    void streamingServicesUpdated(in List<StreamingServiceInfo> services); +    void onStreamingServicesUpdated(in List<StreamingServiceInfo> services); -    void middlewareReady(); +    void onMiddlewareReady();  } diff --git a/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl b/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl index 0952fbea881d..164cefb2d5ef 100755 --- a/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl +++ b/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl @@ -20,9 +20,9 @@ package android.telephony.mbms;   * @hide   */  oneway interface IStreamingServiceCallback { -    void error(int errorCode, String message); -    void streamStateUpdated(int state, int reason); -    void mediaDescriptionUpdated(); -    void broadcastSignalStrengthUpdated(int signalStrength); -    void streamMethodUpdated(int methodType); +    void onError(int errorCode, String message); +    void onStreamStateUpdated(int state, int reason); +    void onMediaDescriptionUpdated(); +    void onBroadcastSignalStrengthUpdated(int signalStrength); +    void onStreamMethodUpdated(int methodType);  } diff --git a/telephony/java/android/telephony/mbms/InternalDownloadManagerCallback.java b/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java index fe2d71989382..a7a5958fff56 100644 --- a/telephony/java/android/telephony/mbms/InternalDownloadManagerCallback.java +++ b/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java @@ -22,19 +22,24 @@ import android.os.RemoteException;  import java.util.List;  /** @hide */ -public class InternalDownloadManagerCallback extends IMbmsDownloadManagerCallback.Stub { +public class InternalDownloadSessionCallback extends IMbmsDownloadSessionCallback.Stub {      private final Handler mHandler; -    private final MbmsDownloadManagerCallback mAppCallback; +    private final MbmsDownloadSessionCallback mAppCallback; +    private volatile boolean mIsStopped = false; -    public InternalDownloadManagerCallback(MbmsDownloadManagerCallback appCallback, +    public InternalDownloadSessionCallback(MbmsDownloadSessionCallback appCallback,              Handler handler) {          mAppCallback = appCallback;          mHandler = handler;      }      @Override -    public void error(final int errorCode, final String message) throws RemoteException { +    public void onError(final int errorCode, final String message) throws RemoteException { +        if (mIsStopped) { +            return; +        } +          mHandler.post(new Runnable() {              @Override              public void run() { @@ -44,7 +49,11 @@ public class InternalDownloadManagerCallback extends IMbmsDownloadManagerCallbac      }      @Override -    public void fileServicesUpdated(final List<FileServiceInfo> services) throws RemoteException { +    public void onFileServicesUpdated(final List<FileServiceInfo> services) throws RemoteException { +        if (mIsStopped) { +            return; +        } +          mHandler.post(new Runnable() {              @Override              public void run() { @@ -54,7 +63,11 @@ public class InternalDownloadManagerCallback extends IMbmsDownloadManagerCallbac      }      @Override -    public void middlewareReady() throws RemoteException { +    public void onMiddlewareReady() throws RemoteException { +        if (mIsStopped) { +            return; +        } +          mHandler.post(new Runnable() {              @Override              public void run() { @@ -66,4 +79,8 @@ public class InternalDownloadManagerCallback extends IMbmsDownloadManagerCallbac      public Handler getHandler() {          return mHandler;      } + +    public void stop() { +        mIsStopped = true; +    }  } diff --git a/telephony/java/android/telephony/mbms/InternalDownloadStateCallback.java b/telephony/java/android/telephony/mbms/InternalDownloadStateCallback.java index 32be16b5bdd9..8702952cf06b 100644 --- a/telephony/java/android/telephony/mbms/InternalDownloadStateCallback.java +++ b/telephony/java/android/telephony/mbms/InternalDownloadStateCallback.java @@ -25,6 +25,7 @@ import android.os.RemoteException;  public class InternalDownloadStateCallback extends IDownloadStateCallback.Stub {      private final Handler mHandler;      private final DownloadStateCallback mAppCallback; +    private volatile boolean mIsStopped = false;      public InternalDownloadStateCallback(DownloadStateCallback appCallback, Handler handler) {          mAppCallback = appCallback; @@ -32,9 +33,13 @@ public class InternalDownloadStateCallback extends IDownloadStateCallback.Stub {      }      @Override -    public void progress(final DownloadRequest request, final FileInfo fileInfo, +    public void onProgressUpdated(final DownloadRequest request, final FileInfo fileInfo,              final int currentDownloadSize, final int fullDownloadSize, final int currentDecodedSize,              final int fullDecodedSize) throws RemoteException { +        if (mIsStopped) { +            return; +        } +          mHandler.post(new Runnable() {              @Override              public void run() { @@ -45,13 +50,21 @@ public class InternalDownloadStateCallback extends IDownloadStateCallback.Stub {      }      @Override -    public void state(final DownloadRequest request, final FileInfo fileInfo, final int state) -            throws RemoteException { +    public void onStateUpdated(final DownloadRequest request, final FileInfo fileInfo, +            final int state) throws RemoteException { +        if (mIsStopped) { +            return; +        } +          mHandler.post(new Runnable() {              @Override              public void run() { -                mAppCallback.onStateChanged(request, fileInfo, state); +                mAppCallback.onStateUpdated(request, fileInfo, state);              }          });      } + +    public void stop() { +        mIsStopped = true; +    }  } diff --git a/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java b/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java index bb337b271cf0..eb6579cec471 100644 --- a/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java +++ b/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java @@ -23,6 +23,7 @@ import android.os.RemoteException;  public class InternalStreamingServiceCallback extends IStreamingServiceCallback.Stub {      private final StreamingServiceCallback mAppCallback;      private final Handler mHandler; +    private volatile boolean mIsStopped = false;      public InternalStreamingServiceCallback(StreamingServiceCallback appCallback, Handler handler) {          mAppCallback = appCallback; @@ -30,7 +31,11 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.      }      @Override -    public void error(int errorCode, String message) throws RemoteException { +    public void onError(final int errorCode, final String message) throws RemoteException { +        if (mIsStopped) { +            return; +        } +          mHandler.post(new Runnable() {              @Override              public void run() { @@ -40,7 +45,11 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.      }      @Override -    public void streamStateUpdated(int state, int reason) throws RemoteException { +    public void onStreamStateUpdated(final int state, final int reason) throws RemoteException { +        if (mIsStopped) { +            return; +        } +          mHandler.post(new Runnable() {              @Override              public void run() { @@ -50,7 +59,11 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.      }      @Override -    public void mediaDescriptionUpdated() throws RemoteException { +    public void onMediaDescriptionUpdated() throws RemoteException { +        if (mIsStopped) { +            return; +        } +          mHandler.post(new Runnable() {              @Override              public void run() { @@ -60,7 +73,11 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.      }      @Override -    public void broadcastSignalStrengthUpdated(int signalStrength) throws RemoteException { +    public void onBroadcastSignalStrengthUpdated(final int signalStrength) throws RemoteException { +        if (mIsStopped) { +            return; +        } +          mHandler.post(new Runnable() {              @Override              public void run() { @@ -70,7 +87,11 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.      }      @Override -    public void streamMethodUpdated(int methodType) throws RemoteException { +    public void onStreamMethodUpdated(final int methodType) throws RemoteException { +        if (mIsStopped) { +            return; +        } +          mHandler.post(new Runnable() {              @Override              public void run() { @@ -78,4 +99,8 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.              }          });      } + +    public void stop() { +        mIsStopped = true; +    }  } diff --git a/telephony/java/android/telephony/mbms/InternalStreamingManagerCallback.java b/telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java index b52df8c0dd84..d782d12c00d6 100644 --- a/telephony/java/android/telephony/mbms/InternalStreamingManagerCallback.java +++ b/telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java @@ -18,25 +18,27 @@ package android.telephony.mbms;  import android.os.Handler;  import android.os.RemoteException; -import android.telephony.mbms.IMbmsStreamingManagerCallback; -import android.telephony.mbms.MbmsStreamingManagerCallback; -import android.telephony.mbms.StreamingServiceInfo;  import java.util.List;  /** @hide */ -public class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallback.Stub { +public class InternalStreamingSessionCallback extends IMbmsStreamingSessionCallback.Stub {      private final Handler mHandler; -    private final MbmsStreamingManagerCallback mAppCallback; +    private final MbmsStreamingSessionCallback mAppCallback; +    private volatile boolean mIsStopped = false; -    public InternalStreamingManagerCallback(MbmsStreamingManagerCallback appCallback, +    public InternalStreamingSessionCallback(MbmsStreamingSessionCallback appCallback,              Handler handler) {          mAppCallback = appCallback;          mHandler = handler;      }      @Override -    public void error(int errorCode, String message) throws RemoteException { +    public void onError(final int errorCode, final String message) throws RemoteException { +        if (mIsStopped) { +            return; +        } +          mHandler.post(new Runnable() {              @Override              public void run() { @@ -46,8 +48,12 @@ public class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallb      }      @Override -    public void streamingServicesUpdated(List<StreamingServiceInfo> services) +    public void onStreamingServicesUpdated(final List<StreamingServiceInfo> services)              throws RemoteException { +        if (mIsStopped) { +            return; +        } +          mHandler.post(new Runnable() {              @Override              public void run() { @@ -57,7 +63,11 @@ public class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallb      }      @Override -    public void middlewareReady() throws RemoteException { +    public void onMiddlewareReady() throws RemoteException { +        if (mIsStopped) { +            return; +        } +          mHandler.post(new Runnable() {              @Override              public void run() { @@ -69,4 +79,8 @@ public class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallb      public Handler getHandler() {          return mHandler;      } + +    public void stop() { +        mIsStopped = true; +    }  } diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java index 7ee337a89934..93a7cad10dea 100644 --- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java +++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java @@ -25,7 +25,7 @@ import android.content.pm.ApplicationInfo;  import android.content.pm.PackageManager;  import android.net.Uri;  import android.os.Bundle; -import android.telephony.MbmsDownloadManager; +import android.telephony.MbmsDownloadSession;  import android.telephony.mbms.vendor.VendorUtils;  import android.util.Log; @@ -36,8 +36,10 @@ import java.io.FileOutputStream;  import java.io.IOException;  import java.io.InputStream;  import java.io.OutputStream; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path;  import java.util.ArrayList; -import java.util.Iterator;  import java.util.List;  import java.util.Objects;  import java.util.UUID; @@ -115,8 +117,19 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {      //@SystemApi      public static final int RESULT_TEMP_FILE_GENERATION_ERROR = 5; +    /** +     * Indicates that the manager was unable to notify the app of the completed download. +     * This is a fatal result code and no result extras should be expected. +     * @hide +     */ +    @SystemApi +    public static final int RESULT_APP_NOTIFICATION_ERROR = 6; + +      private static final String LOG_TAG = "MbmsDownloadReceiver";      private static final String TEMP_FILE_SUFFIX = ".embms.temp"; +    private static final String TEMP_FILE_STAGING_LOCATION = "staged_completed_files"; +      private static final int MAX_TEMP_FILE_RETRIES = 5;      private String mFileProviderAuthorityCache = null; @@ -149,11 +162,11 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {      private boolean verifyIntentContents(Context context, Intent intent) {          if (VendorUtils.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) { -            if (!intent.hasExtra(MbmsDownloadManager.EXTRA_MBMS_DOWNLOAD_RESULT)) { +            if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT)) {                  Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");                  return false;              } -            if (!intent.hasExtra(VendorUtils.EXTRA_REQUEST)) { +            if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST)) {                  Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring.");                  return false;              } @@ -161,7 +174,7 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {                  Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring.");                  return false;              } -            if (!intent.hasExtra(MbmsDownloadManager.EXTRA_MBMS_FILE_INFO)) { +            if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO)) {                  Log.w(LOG_TAG, "Download result did not include the associated file info. " +                          "Ignoring.");                  return false; @@ -171,7 +184,8 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {                          "temp file. Ignoring.");                  return false;              } -            DownloadRequest request = intent.getParcelableExtra(VendorUtils.EXTRA_REQUEST); +            DownloadRequest request = intent.getParcelableExtra( +                    MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST);              String expectedTokenFileName = request.getHash() + DOWNLOAD_TOKEN_SUFFIX;              File expectedTokenFile = new File(                      MbmsUtils.getEmbmsTempFileDirForService(context, request.getFileServiceId()), @@ -211,20 +225,25 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {      }      private void moveDownloadedFile(Context context, Intent intent) { -        DownloadRequest request = intent.getParcelableExtra(VendorUtils.EXTRA_REQUEST); +        DownloadRequest request = intent.getParcelableExtra( +                MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST);          Intent intentForApp = request.getIntentForApp(); +        if (intentForApp == null) { +            Log.i(LOG_TAG, "Malformed app notification intent"); +            setResultCode(RESULT_APP_NOTIFICATION_ERROR); +            return; +        } -        int result = intent.getIntExtra(MbmsDownloadManager.EXTRA_MBMS_DOWNLOAD_RESULT, -                MbmsDownloadManager.RESULT_CANCELLED); -        intentForApp.putExtra(MbmsDownloadManager.EXTRA_MBMS_DOWNLOAD_RESULT, result); +        int result = intent.getIntExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT, +                MbmsDownloadSession.RESULT_CANCELLED); +        intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT, result); -        if (result != MbmsDownloadManager.RESULT_SUCCESSFUL) { +        if (result != MbmsDownloadSession.RESULT_SUCCESSFUL) {              Log.i(LOG_TAG, "Download request indicated a failed download. Aborting.");              context.sendBroadcast(intentForApp);              return;          } -        Uri destinationUri = request.getDestinationUri();          Uri finalTempFile = intent.getParcelableExtra(VendorUtils.EXTRA_FINAL_URI);          if (!verifyTempFilePath(context, request.getFileServiceId(), finalTempFile)) {              Log.w(LOG_TAG, "Download result specified an invalid temp file " + finalTempFile); @@ -233,24 +252,31 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {          }          FileInfo completedFileInfo = -                (FileInfo) intent.getParcelableExtra(MbmsDownloadManager.EXTRA_MBMS_FILE_INFO); -        String relativePath = calculateDestinationFileRelativePath(request, completedFileInfo); +                (FileInfo) intent.getParcelableExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO); +        Path stagingDirectory = FileSystems.getDefault().getPath( +                MbmsTempFileProvider.getEmbmsTempFileDir(context).getPath(), +                TEMP_FILE_STAGING_LOCATION); -        Uri finalFileLocation = moveTempFile(finalTempFile, destinationUri, relativePath); -        if (finalFileLocation == null) { +        Uri stagedFileLocation; +        try { +            stagedFileLocation = stageTempFile(finalTempFile, stagingDirectory); +        } catch (IOException e) {              Log.w(LOG_TAG, "Failed to move temp file to final destination");              setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR);              return;          } -        intentForApp.putExtra(MbmsDownloadManager.EXTRA_MBMS_COMPLETED_FILE_URI, finalFileLocation); -        intentForApp.putExtra(MbmsDownloadManager.EXTRA_MBMS_FILE_INFO, completedFileInfo); +        intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_COMPLETED_FILE_URI, +                stagedFileLocation); +        intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO, completedFileInfo); +        intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, request);          context.sendBroadcast(intentForApp);          setResultCode(RESULT_OK);      }      private void cleanupPostMove(Context context, Intent intent) { -        DownloadRequest request = intent.getParcelableExtra(VendorUtils.EXTRA_REQUEST); +        DownloadRequest request = intent.getParcelableExtra( +                MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST);          if (request == null) {              Log.w(LOG_TAG, "Intent does not include a DownloadRequest. Ignoring.");              return; @@ -404,63 +430,22 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {          }      } -    private static String calculateDestinationFileRelativePath(DownloadRequest request, -            FileInfo info) { -        List<String> filePathComponents = info.getUri().getPathSegments(); -        List<String> requestPathComponents = request.getSourceUri().getPathSegments(); -        Iterator<String> filePathIter = filePathComponents.iterator(); -        Iterator<String> requestPathIter = requestPathComponents.iterator(); - -        StringBuilder pathBuilder = new StringBuilder(); -        // Iterate through the segments of the carrier's URI to the file, along with the segments -        // of the source URI specified in the download request. The relative path is calculated -        // as the tail of the file's URI that does not match the path segments in the source URI. -        while (filePathIter.hasNext()) { -            String currFilePathComponent = filePathIter.next(); -            if (requestPathIter.hasNext()) { -                String requestFilePathComponent = requestPathIter.next(); -                if (requestFilePathComponent.equals(currFilePathComponent)) { -                    continue; -                } -            } -            pathBuilder.append(currFilePathComponent); -            pathBuilder.append('/'); -        } -        // remove the trailing slash -        if (pathBuilder.length() > 0) { -            pathBuilder.deleteCharAt(pathBuilder.length() - 1); -        } -        return pathBuilder.toString(); -    } -      /* -     * Moves a tempfile located at fromPath to a new location at toPath. If -     * toPath is a directory, the destination file will be located at  relativePath -     * underneath toPath. +     * Moves a tempfile located at fromPath to a new location in the staging directory.       */ -    private static Uri moveTempFile(Uri fromPath, Uri toPath, String relativePath) { +    private static Uri stageTempFile(Uri fromPath, Path stagingDirectory) throws IOException {          if (!ContentResolver.SCHEME_FILE.equals(fromPath.getScheme())) {              Log.w(LOG_TAG, "Moving source uri " + fromPath+ " does not have a file scheme");              return null;          } -        if (!ContentResolver.SCHEME_FILE.equals(toPath.getScheme())) { -            Log.w(LOG_TAG, "Moving destination uri " + toPath + " does not have a file scheme"); -            return null; -        } -        File fromFile = new File(fromPath.getSchemeSpecificPart()); -        File toFile = new File(toPath.getSchemeSpecificPart()); -        if (toFile.isDirectory()) { -            toFile = new File(toFile, relativePath); +        Path fromFile = FileSystems.getDefault().getPath(fromPath.getPath()); +        if (!Files.isDirectory(stagingDirectory)) { +            Files.createDirectory(stagingDirectory);          } -        toFile.getParentFile().mkdirs(); +        Path result = Files.move(fromFile, stagingDirectory.resolve(fromFile.getFileName())); -        if (fromFile.renameTo(toFile)) { -            return Uri.fromFile(toFile); -        } else if (manualMove(fromFile, toFile)) { -            return Uri.fromFile(toFile); -        } -        return null; +        return Uri.fromFile(result.toFile());      }      private static boolean verifyTempFilePath(Context context, String serviceId, @@ -512,7 +497,7 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {      private String getMiddlewarePackageCached(Context context) {          if (mMiddlewarePackageNameCache == null) {              mMiddlewarePackageNameCache = MbmsUtils.getMiddlewareServiceInfo(context, -                    MbmsDownloadManager.MBMS_DOWNLOAD_SERVICE_ACTION).packageName; +                    MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_ACTION).packageName;          }          return mMiddlewarePackageNameCache;      } diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java b/telephony/java/android/telephony/mbms/MbmsDownloadSessionCallback.java index 14598443d61d..42bc7759688c 100644 --- a/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java +++ b/telephony/java/android/telephony/mbms/MbmsDownloadSessionCallback.java @@ -16,7 +16,7 @@  package android.telephony.mbms; -import android.telephony.MbmsDownloadManager; +import android.telephony.MbmsDownloadSession;  import java.util.List; @@ -25,11 +25,11 @@ import java.util.List;   * cell-broadcast.   * @hide   */ -public class MbmsDownloadManagerCallback { +public class MbmsDownloadSessionCallback {      /**       * Indicates that the middleware has encountered an asynchronous error. -     * @param errorCode Any error code listed in {@link MbmsException} +     * @param errorCode Any error code listed in {@link MbmsErrors}       * @param message A message, intended for debugging purposes, describing the error in further       *                detail.       */ @@ -42,8 +42,9 @@ public class MbmsDownloadManagerCallback {       *       * This will only be called after the application has requested a list of file services and       * specified a service class list of interest via -     * {@link MbmsDownloadManager#getFileServices(List)}. If there are subsequent calls to -     * {@link MbmsDownloadManager#getFileServices(List)}, this method may not be called again if +     * {@link MbmsDownloadSession#requestUpdateFileServices(List)}. If there are subsequent calls to +     * {@link MbmsDownloadSession#requestUpdateFileServices(List)}, +     * this method may not be called again if       * the list of service classes would remain the same.       *       * @param services The most recently updated list of available file services. @@ -56,9 +57,9 @@ public class MbmsDownloadManagerCallback {       * Called to indicate that the middleware has been initialized and is ready.       *       * Before this method is called, calling any method on an instance of -     * {@link android.telephony.MbmsDownloadManager} will result in an {@link MbmsException} -     * being thrown with error code {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND} -     * or {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY} +     * {@link MbmsDownloadSession} will result in an {@link IllegalStateException} +     * being thrown or {@link #onError(int, String)} being called with error code +     * {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}       */      public void onMiddlewareReady() {          // default implementation empty diff --git a/telephony/java/android/telephony/mbms/MbmsException.java b/telephony/java/android/telephony/mbms/MbmsErrors.java index 144a8a0eeab0..01958699b6b8 100644 --- a/telephony/java/android/telephony/mbms/MbmsException.java +++ b/telephony/java/android/telephony/mbms/MbmsErrors.java @@ -16,8 +16,10 @@  package android.telephony.mbms; +import android.telephony.MbmsStreamingSession; +  /** @hide */ -public class MbmsException extends Exception { +public class MbmsErrors {      /** Indicates that the operation was successful. */      public static final int SUCCESS = 0; @@ -31,8 +33,8 @@ public class MbmsException extends Exception {      /**       * Indicates that the app attempted to perform an operation on an instance of -     * TODO link android.telephony.MbmsDownloadManager or -     * {@link android.telephony.MbmsStreamingManager} without being bound to the middleware. +     * {@link android.telephony.MbmsDownloadSession} or +     * {@link MbmsStreamingSession} without being bound to the middleware.       */      public static final int ERROR_MIDDLEWARE_NOT_BOUND = 2; @@ -47,8 +49,7 @@ public class MbmsException extends Exception {          private InitializationErrors() {}          /**           * Indicates that the app tried to create more than one instance each of -         * {@link android.telephony.MbmsStreamingManager} or -         * TODO link android.telephony.MbmsDownloadManager +         * {@link MbmsStreamingSession} or {@link android.telephony.MbmsDownloadSession}.           */          public static final int ERROR_DUPLICATE_INITIALIZE = 101;          /** Indicates that the app is not authorized to access media via MBMS.*/ @@ -65,8 +66,8 @@ public class MbmsException extends Exception {          private GeneralErrors() {}          /**           * Indicates that the app attempted to perform an operation before receiving notification -         * that the middleware is ready via {@link MbmsStreamingManagerCallback#onMiddlewareReady()} -         * or TODO: link MbmsDownloadManagerCallback#middlewareReady +         * that the middleware is ready via {@link MbmsStreamingSessionCallback#onMiddlewareReady()} +         * or {@link MbmsDownloadSessionCallback#onMiddlewareReady()}.           */          public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 201;          /** @@ -108,7 +109,7 @@ public class MbmsException extends Exception {          /**           * Indicates that the app called -         * {@link android.telephony.MbmsStreamingManager#startStreaming( +         * {@link MbmsStreamingSession#startStreaming(           * StreamingServiceInfo, StreamingServiceCallback, android.os.Handler)}           * more than once for the same {@link StreamingServiceInfo}.           */ @@ -130,15 +131,5 @@ public class MbmsException extends Exception {          public static final int ERROR_UNKNOWN_DOWNLOAD_REQUEST = 402;      } -    private final int mErrorCode; - -    /** @hide */ -    public MbmsException(int errorCode) { -        super(); -        mErrorCode = errorCode; -    } - -    public int getErrorCode() { -        return mErrorCode; -    } +    private MbmsErrors() {}  } diff --git a/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java b/telephony/java/android/telephony/mbms/MbmsStreamingSessionCallback.java index 831050efdd47..d714927af97b 100644 --- a/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java +++ b/telephony/java/android/telephony/mbms/MbmsStreamingSessionCallback.java @@ -16,26 +16,27 @@  package android.telephony.mbms; +import android.annotation.Nullable;  import android.content.Context; -import android.os.RemoteException; -import android.telephony.MbmsStreamingManager; +import android.os.Handler; +import android.telephony.MbmsStreamingSession;  import java.util.List;  /**   * A callback class that is used to receive information from the middleware on MBMS streaming   * services. An instance of this object should be passed into - * {@link android.telephony.MbmsStreamingManager#create(Context, MbmsStreamingManagerCallback)}. + * {@link MbmsStreamingSession#create(Context, MbmsStreamingSessionCallback, int, Handler)}.   * @hide   */ -public class MbmsStreamingManagerCallback { +public class MbmsStreamingSessionCallback {      /**       * Called by the middleware when it has detected an error condition. The possible error codes -     * are listed in {@link MbmsException}. +     * are listed in {@link MbmsErrors}.       * @param errorCode The error code.       * @param message A human-readable message generated by the middleware for debugging purposes.       */ -    public void onError(int errorCode, String message) { +    public void onError(int errorCode, @Nullable String message) {          // default implementation empty      } @@ -48,8 +49,7 @@ public class MbmsStreamingManagerCallback {       * call with the same service class list would return different       * results.       * -     * @param services a List of StreamingServiceInfos -     * +     * @param services The list of available services.       */      public void onStreamingServicesUpdated(List<StreamingServiceInfo> services) {          // default implementation empty @@ -59,9 +59,9 @@ public class MbmsStreamingManagerCallback {       * Called to indicate that the middleware has been initialized and is ready.       *       * Before this method is called, calling any method on an instance of -     * {@link android.telephony.MbmsStreamingManager} will result in an {@link MbmsException} -     * being thrown with error code {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND} -     * or {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY} +     * {@link MbmsStreamingSession} will result in an {@link IllegalStateException} or an error +     * delivered via {@link #onError(int, String)} with error code +     * {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}.       */      public void onMiddlewareReady() {          // default implementation empty diff --git a/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java b/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java index 190ec8b6c069..689becd7169a 100644 --- a/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java +++ b/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java @@ -27,7 +27,7 @@ import android.content.pm.ProviderInfo;  import android.database.Cursor;  import android.net.Uri;  import android.os.ParcelFileDescriptor; -import android.telephony.MbmsDownloadManager; +import android.telephony.MbmsDownloadSession;  import java.io.File;  import java.io.FileNotFoundException; @@ -181,7 +181,7 @@ public class MbmsTempFileProvider extends ContentProvider {                  return new File(storedTempFileRoot).getCanonicalFile();              } else {                  return new File(context.getFilesDir(), -                        MbmsDownloadManager.DEFAULT_TOP_LEVEL_TEMP_DIRECTORY).getCanonicalFile(); +                        MbmsDownloadSession.DEFAULT_TOP_LEVEL_TEMP_DIRECTORY).getCanonicalFile();              }          } catch (IOException e) {              throw new RuntimeException("Unable to canonicalize temp file root path " + e); diff --git a/telephony/java/android/telephony/mbms/MbmsUtils.java b/telephony/java/android/telephony/mbms/MbmsUtils.java index 4b913f825231..d38d8a712c73 100644 --- a/telephony/java/android/telephony/mbms/MbmsUtils.java +++ b/telephony/java/android/telephony/mbms/MbmsUtils.java @@ -68,19 +68,20 @@ public class MbmsUtils {          return downloadServices.get(0).serviceInfo;      } -    public static void startBinding(Context context, String serviceAction, -            ServiceConnection serviceConnection) throws MbmsException { +    public static int startBinding(Context context, String serviceAction, +            ServiceConnection serviceConnection) {          Intent bindIntent = new Intent();          ServiceInfo mbmsServiceInfo =                  MbmsUtils.getMiddlewareServiceInfo(context, serviceAction);          if (mbmsServiceInfo == null) { -            throw new MbmsException(MbmsException.ERROR_NO_UNIQUE_MIDDLEWARE); +            return MbmsErrors.ERROR_NO_UNIQUE_MIDDLEWARE;          }          bindIntent.setComponent(MbmsUtils.toComponentName(mbmsServiceInfo));          context.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE); +        return MbmsErrors.SUCCESS;      }      /** diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java index 57f34954206f..3b9cc0e762eb 100644 --- a/telephony/java/android/telephony/mbms/ServiceInfo.java +++ b/telephony/java/android/telephony/mbms/ServiceInfo.java @@ -16,6 +16,8 @@  package android.telephony.mbms; +import android.annotation.NonNull; +import android.annotation.Nullable;  import android.os.Parcel;  import android.os.Parcelable;  import android.text.TextUtils; @@ -26,12 +28,13 @@ import java.util.HashMap;  import java.util.List;  import java.util.Locale;  import java.util.Map; +import java.util.NoSuchElementException;  import java.util.Objects;  import java.util.Set;  /**   * Describes a cell-broadcast service. This class should not be instantiated directly -- use - * {@link StreamingServiceInfo} or TODO link FileServiceInfo + * {@link StreamingServiceInfo} or {@link FileServiceInfo}   * @hide   */  public class ServiceInfo { @@ -59,6 +62,13 @@ public class ServiceInfo {          if (newLocales.size() > MAP_LIMIT) {              throw new RuntimeException("bad locales length " + newLocales.size());          } + +        for (Locale l : newLocales) { +            if (!newNames.containsKey(l)) { +                throw new IllegalArgumentException("A name must be provided for each locale"); +            } +        } +          names = new HashMap(newNames.size());          names.putAll(newNames);          className = newClassName; @@ -115,16 +125,25 @@ public class ServiceInfo {      }      /** -     * User displayable names listed by language. Do not modify the map returned from this method. +     * Get the user-displayable name for this cell-broadcast service corresponding to the +     * provided {@link Locale}. +     * @param locale The {@link Locale} in which you want the name of the service. This must be a +     *               value from the list returned by {@link #getLocales()} -- an +     *               {@link java.util.NoSuchElementException} may be thrown otherwise. +     * @return The {@link CharSequence} providing the name of the service in the given +     *         {@link Locale}       */ -    public Map<Locale, String> getNames() { -        return names; +    public @NonNull CharSequence getNameForLocale(@NonNull Locale locale) { +        if (!names.containsKey(locale)) { +            throw new NoSuchElementException("Locale not supported"); +        } +        return names.get(locale);      }      /**       * The class name for this service - used to categorize and filter       */ -    public String getClassName() { +    public String getServiceClassName() {          return className;      } diff --git a/telephony/java/android/telephony/mbms/StreamingService.java b/telephony/java/android/telephony/mbms/StreamingService.java index 5c4b7862289f..ea9d70ab0bc8 100644 --- a/telephony/java/android/telephony/mbms/StreamingService.java +++ b/telephony/java/android/telephony/mbms/StreamingService.java @@ -17,8 +17,10 @@  package android.telephony.mbms;  import android.annotation.IntDef; +import android.annotation.Nullable;  import android.net.Uri;  import android.os.RemoteException; +import android.telephony.MbmsStreamingSession;  import android.telephony.mbms.vendor.IMbmsStreamingService;  import android.util.Log; @@ -27,7 +29,7 @@ import java.lang.annotation.RetentionPolicy;  /**   * Class used to represent a single MBMS stream. After a stream has been started with - * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo, + * {@link MbmsStreamingSession#startStreaming(StreamingServiceInfo,   * StreamingServiceCallback, android.os.Handler)},   * this class is used to hold information about the stream and control it.   * @hide @@ -64,7 +66,7 @@ public class StreamingService {      /**       * State changed due to a call to {@link #stopStreaming()} or -     * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo, +     * {@link MbmsStreamingSession#startStreaming(StreamingServiceInfo,       * StreamingServiceCallback, android.os.Handler)}       */      public static final int REASON_BY_USER_REQUEST = 1; @@ -102,6 +104,7 @@ public class StreamingService {      public final static int UNICAST_METHOD   = 2;      private final int mSubscriptionId; +    private final MbmsStreamingSession mParentSession;      private final StreamingServiceInfo mServiceInfo;      private final InternalStreamingServiceCallback mCallback; @@ -112,25 +115,25 @@ public class StreamingService {       */      public StreamingService(int subscriptionId,              IMbmsStreamingService service, +            MbmsStreamingSession session,              StreamingServiceInfo streamingServiceInfo,              InternalStreamingServiceCallback callback) {          mSubscriptionId = subscriptionId; +        mParentSession = session;          mService = service;          mServiceInfo = streamingServiceInfo;          mCallback = callback;      }      /** -     * Retreive the Uri used to play this stream. +     * Retrieve the Uri used to play this stream.       * -     * This may throw a {@link MbmsException} with the error code -     * {@link MbmsException#ERROR_MIDDLEWARE_LOST} +     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}.       * -     * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException} -     * -     * @return The {@link Uri} to pass to the streaming client. +     * @return The {@link Uri} to pass to the streaming client, or {@code null} if an error +     *         occurred.       */ -    public Uri getPlaybackUri() throws MbmsException { +    public @Nullable Uri getPlaybackUri() {          if (mService == null) {              throw new IllegalStateException("No streaming service attached");          } @@ -140,25 +143,26 @@ public class StreamingService {          } catch (RemoteException e) {              Log.w(LOG_TAG, "Remote process died");              mService = null; -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); +            mParentSession.onStreamingServiceStopped(this); +            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); +            return null;          }      }      /** -     * Retreive the info for this StreamingService. +     * Retrieve the {@link StreamingServiceInfo} corresponding to this stream.       */      public StreamingServiceInfo getInfo() {          return mServiceInfo;      }      /** -     * Stop streaming this service. -     * This may throw a {@link MbmsException} with the error code -     * {@link MbmsException#ERROR_MIDDLEWARE_LOST} +     * Stop streaming this service. Further operations on this object will fail with an +     * {@link IllegalStateException}.       * -     * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException} +     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}       */ -    public void stopStreaming() throws MbmsException { +    public void stopStreaming() {          if (mService == null) {              throw new IllegalStateException("No streaming service attached");          } @@ -168,32 +172,22 @@ public class StreamingService {          } catch (RemoteException e) {              Log.w(LOG_TAG, "Remote process died");              mService = null; -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); +            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); +        } finally { +            mParentSession.onStreamingServiceStopped(this);          }      } -    /** -     * Disposes of this stream. Further operations on this object will fail with an -     * {@link IllegalStateException}. -     * -     * This may throw a {@link MbmsException} with the error code -     * {@link MbmsException#ERROR_MIDDLEWARE_LOST} -     * May also throw an {@link IllegalStateException} -     */ -    public void dispose() throws MbmsException { -        if (mService == null) { -            throw new IllegalStateException("No streaming service attached"); -        } +    /** @hide */ +    public InternalStreamingServiceCallback getCallback() { +        return mCallback; +    } +    private void sendErrorToApp(int errorCode, String message) {          try { -            mService.disposeStream(mSubscriptionId, mServiceInfo.getServiceId()); +            mCallback.onError(errorCode, message);          } catch (RemoteException e) { -            Log.w(LOG_TAG, "Remote process died"); -            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); -        } catch (IllegalArgumentException e) { -            throw new IllegalStateException("StreamingService state inconsistent with middleware"); -        } finally { -            mService = null; +            // Ignore, should not happen locally.          }      }  } diff --git a/telephony/java/android/telephony/mbms/StreamingServiceCallback.java b/telephony/java/android/telephony/mbms/StreamingServiceCallback.java index eeef8bcab04f..3c079613cce8 100644 --- a/telephony/java/android/telephony/mbms/StreamingServiceCallback.java +++ b/telephony/java/android/telephony/mbms/StreamingServiceCallback.java @@ -16,6 +16,8 @@  package android.telephony.mbms; +import android.annotation.Nullable; +  /**   * A callback class for use when the application is actively streaming content. The middleware   * will provide updates on the status of the stream via this callback. @@ -34,11 +36,11 @@ public class StreamingServiceCallback {      /**       * Called by the middleware when it has detected an error condition in this stream. The -     * possible error codes are listed in {@link MbmsException}. +     * possible error codes are listed in {@link MbmsErrors}.       * @param errorCode The error code.       * @param message A human-readable message generated by the middleware for debugging purposes.       */ -    public void onError(int errorCode, String message) { +    public void onError(int errorCode, @Nullable String message) {          // default implementation empty      } diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl index f29499d9cb31..ed5e8268fc77 100755 --- a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl +++ b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl @@ -20,7 +20,7 @@ import android.app.PendingIntent;  import android.net.Uri;  import android.telephony.mbms.DownloadRequest;  import android.telephony.mbms.FileInfo; -import android.telephony.mbms.IMbmsDownloadManagerCallback; +import android.telephony.mbms.IMbmsDownloadSessionCallback;  import android.telephony.mbms.IDownloadStateCallback;  /** @@ -28,13 +28,18 @@ import android.telephony.mbms.IDownloadStateCallback;   */  interface IMbmsDownloadService  { -    int initialize(int subId, IMbmsDownloadManagerCallback listener); +    int initialize(int subId, IMbmsDownloadSessionCallback listener); -    int getFileServices(int subId, in List<String> serviceClasses); +    int requestUpdateFileServices(int subId, in List<String> serviceClasses);      int setTempFileRootDirectory(int subId, String rootDirectoryPath); -    int download(in DownloadRequest downloadRequest, IDownloadStateCallback listener); +    int download(in DownloadRequest downloadRequest); + +    int registerStateCallback(in DownloadRequest downloadRequest, IDownloadStateCallback listener); + +    int unregisterStateCallback(in DownloadRequest downloadRequest, +        IDownloadStateCallback listener);      List<DownloadRequest> listPendingDownloads(int subscriptionId); diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl index 4dd42924ab05..c90ffc7726e4 100755 --- a/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl +++ b/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl @@ -17,7 +17,7 @@  package android.telephony.mbms.vendor;  import android.net.Uri; -import android.telephony.mbms.IMbmsStreamingManagerCallback; +import android.telephony.mbms.IMbmsStreamingSessionCallback;  import android.telephony.mbms.IStreamingServiceCallback;  import android.telephony.mbms.StreamingServiceInfo; @@ -26,18 +26,16 @@ import android.telephony.mbms.StreamingServiceInfo;   */  interface IMbmsStreamingService  { -    int initialize(IMbmsStreamingManagerCallback listener, int subId); +    int initialize(IMbmsStreamingSessionCallback callback, int subId); -    int getStreamingServices(int subId, in List<String> serviceClasses); +    int requestUpdateStreamingServices(int subId, in List<String> serviceClasses);      int startStreaming(int subId, String serviceId, -            IStreamingServiceCallback listener); +            IStreamingServiceCallback callback);      Uri getPlaybackUri(int subId, String serviceId);      void stopStreaming(int subId, String serviceId); -    void disposeStream(int subId, String serviceId); -      void dispose(int subId);  } diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java index 84ff53324b13..be686eec274b 100644 --- a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java +++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java @@ -20,17 +20,21 @@ import android.annotation.NonNull;  import android.annotation.SystemApi;  import android.content.Intent;  import android.os.Binder; +import android.os.IBinder;  import android.os.RemoteException; -import android.telephony.mbms.DownloadStateCallback; +import android.telephony.MbmsDownloadSession;  import android.telephony.mbms.DownloadRequest; +import android.telephony.mbms.DownloadStateCallback;  import android.telephony.mbms.FileInfo;  import android.telephony.mbms.FileServiceInfo;  import android.telephony.mbms.IDownloadStateCallback; -import android.telephony.mbms.IMbmsDownloadManagerCallback; -import android.telephony.mbms.MbmsDownloadManagerCallback; -import android.telephony.mbms.MbmsException; +import android.telephony.mbms.IMbmsDownloadSessionCallback; +import android.telephony.mbms.MbmsDownloadSessionCallback; +import android.telephony.mbms.MbmsErrors; +import java.util.HashMap;  import java.util.List; +import java.util.Map;  /**   * Base class for MbmsDownloadService. The middleware should return an instance of this object from @@ -39,21 +43,24 @@ import java.util.List;   */  //@SystemApi  public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { +    private final Map<IBinder, DownloadStateCallback> mDownloadCallbackBinderMap = new HashMap<>(); +    private final Map<IBinder, DeathRecipient> mDownloadCallbackDeathRecipients = new HashMap<>(); +      /**       * Initialize the download service for this app and subId, registering the listener.       *       * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}, which       * will be intercepted and passed to the app as -     * {@link android.telephony.mbms.MbmsException.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE} +     * {@link MbmsErrors.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}       * -     * May return any value from {@link android.telephony.mbms.MbmsException.InitializationErrors} -     * or {@link MbmsException#SUCCESS}. Non-successful error codes will be passed to the app via -     * {@link IMbmsDownloadManagerCallback#error(int, String)}. +     * May return any value from {@link MbmsErrors.InitializationErrors} +     * or {@link MbmsErrors#SUCCESS}. Non-successful error codes will be passed to the app via +     * {@link IMbmsDownloadSessionCallback#onError(int, String)}.       *       * @param callback The callback to use to communicate with the app.       * @param subscriptionId The subscription ID to use.       */ -    public int initialize(int subscriptionId, MbmsDownloadManagerCallback callback) +    public int initialize(int subscriptionId, MbmsDownloadSessionCallback callback)              throws RemoteException {          return 0;      } @@ -64,7 +71,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {       */      @Override      public final int initialize(final int subscriptionId, -            final IMbmsDownloadManagerCallback callback) throws RemoteException { +            final IMbmsDownloadSessionCallback callback) throws RemoteException {          final int uid = Binder.getCallingUid();          callback.asBinder().linkToDeath(new DeathRecipient() {              @Override @@ -73,11 +80,11 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {              }          }, 0); -        return initialize(subscriptionId, new MbmsDownloadManagerCallback() { +        return initialize(subscriptionId, new MbmsDownloadSessionCallback() {              @Override              public void onError(int errorCode, String message) {                  try { -                    callback.error(errorCode, message); +                    callback.onError(errorCode, message);                  } catch (RemoteException e) {                      onAppCallbackDied(uid, subscriptionId);                  } @@ -86,7 +93,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {              @Override              public void onFileServicesUpdated(List<FileServiceInfo> services) {                  try { -                    callback.fileServicesUpdated(services); +                    callback.onFileServicesUpdated(services);                  } catch (RemoteException e) {                      onAppCallbackDied(uid, subscriptionId);                  } @@ -95,7 +102,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {              @Override              public void onMiddlewareReady() {                  try { -                    callback.middlewareReady(); +                    callback.onMiddlewareReady();                  } catch (RemoteException e) {                      onAppCallbackDied(uid, subscriptionId);                  } @@ -106,7 +113,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {      /**       * Registers serviceClasses of interest with the appName/subId key.       * Starts async fetching data on streaming services of matching classes to be reported -     * later via {@link IMbmsDownloadManagerCallback#fileServicesUpdated(List)} +     * later via {@link IMbmsDownloadSessionCallback#onFileServicesUpdated(List)}       *       * Note that subsequent calls with the same uid and subId will replace       * the service class list. @@ -117,11 +124,11 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {       * @param serviceClasses The service classes that the app wishes to get info on. The strings       *                       may contain arbitrary data as negotiated between the app and the       *                       carrier. -     * @return One of {@link MbmsException#SUCCESS} or -     *         {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}, +     * @return One of {@link MbmsErrors#SUCCESS} or +     *         {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY},       */      @Override -    public int getFileServices(int subscriptionId, List<String> serviceClasses) +    public int requestUpdateFileServices(int subscriptionId, List<String> serviceClasses)              throws RemoteException {          return 0;      } @@ -133,13 +140,13 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {       *       * If the calling app (as identified by the calling UID) currently has any pending download       * requests that have not been canceled, the middleware must return -     * {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} here. +     * {@link MbmsErrors.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} here.       *       * @param subscriptionId The subscription id the download is operating under.       * @param rootDirectoryPath The path to the app's temp file root directory. -     * @return {@link MbmsException#SUCCESS}, -     *         {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY} or -     *         {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} +     * @return {@link MbmsErrors#SUCCESS}, +     *         {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY} or +     *         {@link MbmsErrors.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT}       */      @Override      public int setTempFileRootDirectory(int subscriptionId, @@ -155,12 +162,32 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {       * this is not the case, an {@link IllegalStateException} may be thrown.       *       * @param downloadRequest An object describing the set of files to be downloaded. -     * @param callback A callback through which the middleware can provide progress updates to -     *                 the app while both are still running. -     * @return Any error from {@link android.telephony.mbms.MbmsException.GeneralErrors} -     *         or {@link MbmsException#SUCCESS} +     * @return Any error from {@link MbmsErrors.GeneralErrors} +     *         or {@link MbmsErrors#SUCCESS} +     */ +    @Override +    public int download(DownloadRequest downloadRequest) throws RemoteException { +        return 0; +    } + +    /** +     * Registers a download state callbacks for the provided {@link DownloadRequest}. +     * +     * This method is called by the app when it wants to request updates on the progress or +     * status of the download. +     * +     * If the middleware is not aware of a download having been requested with the provided +     * +     * {@link DownloadRequest} in the past, +     * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST} +     * must be returned. +     * +     * @param downloadRequest The {@link DownloadRequest} that was used to initiate the download +     *                        for which progress updates are being requested. +     * @param callback The callback object to use.       */ -    public int download(DownloadRequest downloadRequest, DownloadStateCallback callback) { +    public int registerStateCallback(DownloadRequest downloadRequest, +            DownloadStateCallback callback) throws RemoteException {          return 0;      } @@ -169,36 +196,101 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {       * @hide       */      @Override -    public final int download(DownloadRequest downloadRequest, IDownloadStateCallback callback) +    public final int registerStateCallback( +            final DownloadRequest downloadRequest, final IDownloadStateCallback callback)              throws RemoteException {          final int uid = Binder.getCallingUid(); -        callback.asBinder().linkToDeath(new DeathRecipient() { +        DeathRecipient deathRecipient = new DeathRecipient() {              @Override              public void binderDied() {                  onAppCallbackDied(uid, downloadRequest.getSubscriptionId()); +                mDownloadCallbackBinderMap.remove(callback.asBinder()); +                mDownloadCallbackDeathRecipients.remove(callback.asBinder());              } -        }, 0); +        }; +        mDownloadCallbackDeathRecipients.put(callback.asBinder(), deathRecipient); +        callback.asBinder().linkToDeath(deathRecipient, 0); -        return download(downloadRequest, new DownloadStateCallback() { +        DownloadStateCallback exposedCallback = new DownloadStateCallback() {              @Override              public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo, int                      currentDownloadSize, int fullDownloadSize, int currentDecodedSize, int                      fullDecodedSize) {                  try { -                    callback.progress(request, fileInfo, currentDownloadSize, fullDownloadSize, +                    callback.onProgressUpdated(request, fileInfo, currentDownloadSize, +                            fullDownloadSize,                              currentDecodedSize, fullDecodedSize);                  } catch (RemoteException e) {                      onAppCallbackDied(uid, downloadRequest.getSubscriptionId());                  }              } -        }); + +            @Override +            public void onStateUpdated(DownloadRequest request, FileInfo fileInfo, +                    @MbmsDownloadSession.DownloadStatus int state) { +                try { +                    callback.onStateUpdated(request, fileInfo, state); +                } catch (RemoteException e) { +                    onAppCallbackDied(uid, downloadRequest.getSubscriptionId()); +                } +            } +        }; + +        mDownloadCallbackBinderMap.put(callback.asBinder(), exposedCallback); + +        return registerStateCallback(downloadRequest, exposedCallback); +    } + +    /** +     * Un-registers a download state callbacks for the provided {@link DownloadRequest}. +     * +     * This method is called by the app when it no longer wants to request updates on the +     * download. +     * +     * If the middleware is not aware of a download having been requested with the provided +     * {@link DownloadRequest} in the past, +     * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST} +     * must be returned. +     * +     * @param downloadRequest The {@link DownloadRequest} that was used to register the callback +     * @param callback The callback object that +     *                 {@link #registerStateCallback(DownloadRequest, DownloadStateCallback)} +     *                 was called with. +     */ +    public int unregisterStateCallback(DownloadRequest downloadRequest, +            DownloadStateCallback callback) throws RemoteException { +        return 0;      } +    /** +     * Actual AIDL implementation -- hides the callback AIDL from the API. +     * @hide +     */ +    @Override +    public final int unregisterStateCallback( +            final DownloadRequest downloadRequest, final IDownloadStateCallback callback) +            throws RemoteException { +        DeathRecipient deathRecipient = +                mDownloadCallbackDeathRecipients.remove(callback.asBinder()); +        if (deathRecipient == null) { +            throw new IllegalArgumentException("Unknown callback"); +        } + +        callback.asBinder().unlinkToDeath(deathRecipient, 0); + +        DownloadStateCallback exposedCallback = +                mDownloadCallbackBinderMap.remove(callback.asBinder()); +        if (exposedCallback == null) { +            throw new IllegalArgumentException("Unknown callback"); +        } + +        return unregisterStateCallback(downloadRequest, exposedCallback); +    }      /**       * Returns a list of pending {@link DownloadRequest}s that originated from the calling       * application, identified by its uid. A pending request is one that was issued via -     * {@link #download(DownloadRequest, DownloadStateCallback)} but not cancelled through +     * {@link #download(DownloadRequest)} but not cancelled through       * {@link #cancelDownload(DownloadRequest)}.       * The middleware must return a non-null result synchronously or throw an exception       * inheriting from {@link RuntimeException}. @@ -214,13 +306,13 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {       * Issues a request to cancel the specified download request.       *       * If the middleware is unable to cancel the request for whatever reason, it should return -     * synchronously with an error. If this method returns {@link MbmsException#SUCCESS}, the app +     * synchronously with an error. If this method returns {@link MbmsErrors#SUCCESS}, the app       * will no longer be expecting any more file-completed intents from the middleware for this       * {@link DownloadRequest}.       * @param downloadRequest The request to cancel -     * @return {@link MbmsException#SUCCESS}, -     *         {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}, -     *         {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY} +     * @return {@link MbmsErrors#SUCCESS}, +     *         {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}, +     *         {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}       */      @Override      public int cancelDownload(DownloadRequest downloadRequest) throws RemoteException { @@ -232,7 +324,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {       *       * If the middleware has not yet been properly initialized or if it has no records of the       * file indicated by {@code fileInfo} being associated with {@code downloadRequest}, -     * {@link android.telephony.MbmsDownloadManager#STATUS_UNKNOWN} must be returned. +     * {@link MbmsDownloadSession#STATUS_UNKNOWN} must be returned.       *       * @param downloadRequest The download request to query.       * @param fileInfo The particular file within the request to get information on. @@ -252,7 +344,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {       * In addition, current in-progress downloads must not be interrupted.       *       * If the middleware is not aware of the specified download request, return -     * {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}. +     * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.       *       * @param downloadRequest The request to re-download files for.       */ @@ -266,7 +358,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {       * Signals that the app wishes to dispose of the session identified by the       * {@code subscriptionId} argument and the caller's uid. No notification back to the       * app is required for this operation, and the corresponding callback provided via -     * {@link #initialize(int, IMbmsDownloadManagerCallback)} should no longer be used +     * {@link #initialize(int, IMbmsDownloadSessionCallback)} should no longer be used       * after this method has been called by the app.       *       * Any download requests issued by the app should remain in effect until the app calls diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java index ecff62cc8190..f998f2ec8278 100644 --- a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java +++ b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java @@ -22,10 +22,10 @@ import android.content.Intent;  import android.net.Uri;  import android.os.Binder;  import android.os.RemoteException; -import android.telephony.mbms.IMbmsStreamingManagerCallback; +import android.telephony.mbms.IMbmsStreamingSessionCallback;  import android.telephony.mbms.IStreamingServiceCallback; -import android.telephony.mbms.MbmsException; -import android.telephony.mbms.MbmsStreamingManagerCallback; +import android.telephony.mbms.MbmsErrors; +import android.telephony.mbms.MbmsStreamingSessionCallback;  import android.telephony.mbms.StreamingService;  import android.telephony.mbms.StreamingServiceCallback;  import android.telephony.mbms.StreamingServiceInfo; @@ -44,16 +44,16 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {       *       * May throw an {@link IllegalArgumentException} or a {@link SecurityException}, which       * will be intercepted and passed to the app as -     * {@link android.telephony.mbms.MbmsException.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE} +     * {@link MbmsErrors.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}       * -     * May return any value from {@link android.telephony.mbms.MbmsException.InitializationErrors} -     * or {@link MbmsException#SUCCESS}. Non-successful error codes will be passed to the app via -     * {@link IMbmsStreamingManagerCallback#error(int, String)}. +     * May return any value from {@link MbmsErrors.InitializationErrors} +     * or {@link MbmsErrors#SUCCESS}. Non-successful error codes will be passed to the app via +     * {@link IMbmsStreamingSessionCallback#onError(int, String)}.       *       * @param callback The callback to use to communicate with the app.       * @param subscriptionId The subscription ID to use.       */ -    public int initialize(MbmsStreamingManagerCallback callback, int subscriptionId) +    public int initialize(MbmsStreamingSessionCallback callback, int subscriptionId)              throws RemoteException {          return 0;      } @@ -63,7 +63,7 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {       * @hide       */      @Override -    public final int initialize(final IMbmsStreamingManagerCallback callback, +    public final int initialize(final IMbmsStreamingSessionCallback callback,              final int subscriptionId) throws RemoteException {          final int uid = Binder.getCallingUid();          callback.asBinder().linkToDeath(new DeathRecipient() { @@ -73,20 +73,20 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {              }          }, 0); -        return initialize(new MbmsStreamingManagerCallback() { +        return initialize(new MbmsStreamingSessionCallback() {              @Override -            public void onError(int errorCode, String message) { +            public void onError(final int errorCode, final String message) {                  try { -                    callback.error(errorCode, message); +                    callback.onError(errorCode, message);                  } catch (RemoteException e) {                      onAppCallbackDied(uid, subscriptionId);                  }              }              @Override -            public void onStreamingServicesUpdated(List<StreamingServiceInfo> services) { +            public void onStreamingServicesUpdated(final List<StreamingServiceInfo> services) {                  try { -                    callback.streamingServicesUpdated(services); +                    callback.onStreamingServicesUpdated(services);                  } catch (RemoteException e) {                      onAppCallbackDied(uid, subscriptionId);                  } @@ -95,7 +95,7 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {              @Override              public void onMiddlewareReady() {                  try { -                    callback.middlewareReady(); +                    callback.onMiddlewareReady();                  } catch (RemoteException e) {                      onAppCallbackDied(uid, subscriptionId);                  } @@ -107,7 +107,7 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {      /**       * Registers serviceClasses of interest with the appName/subId key.       * Starts async fetching data on streaming services of matching classes to be reported -     * later via {@link IMbmsStreamingManagerCallback#streamingServicesUpdated(List)} +     * later via {@link IMbmsStreamingSessionCallback#onStreamingServicesUpdated(List)}       *       * Note that subsequent calls with the same uid and subId will replace       * the service class list. @@ -118,11 +118,11 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {       * @param serviceClasses The service classes that the app wishes to get info on. The strings       *                       may contain arbitrary data as negotiated between the app and the       *                       carrier. -     * @return {@link MbmsException#SUCCESS} or any of the errors in -     * {@link android.telephony.mbms.MbmsException.GeneralErrors} +     * @return {@link MbmsErrors#SUCCESS} or any of the errors in +     * {@link MbmsErrors.GeneralErrors}       */      @Override -    public int getStreamingServices(int subscriptionId, +    public int requestUpdateStreamingServices(int subscriptionId,              List<String> serviceClasses) throws RemoteException {          return 0;      } @@ -130,14 +130,14 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {      /**       * Starts streaming on a particular service. This method may perform asynchronous work. When       * the middleware is ready to send bits to the frontend, it should inform the app via -     * {@link IStreamingServiceCallback#streamStateUpdated(int, int)}. +     * {@link IStreamingServiceCallback#onStreamStateUpdated(int, int)}.       *       * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}       *       * @param subscriptionId The subscription id to use.       * @param serviceId The ID of the streaming service that the app has requested.       * @param callback The callback object on which the app wishes to receive updates. -     * @return Any error in {@link android.telephony.mbms.MbmsException.GeneralErrors} +     * @return Any error in {@link MbmsErrors.GeneralErrors}       */      public int startStreaming(int subscriptionId, String serviceId,              StreamingServiceCallback callback) throws RemoteException { @@ -150,8 +150,8 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {       * @hide       */      @Override -    public int startStreaming(int subscriptionId, String serviceId, -            IStreamingServiceCallback callback) throws RemoteException { +    public int startStreaming(final int subscriptionId, String serviceId, +            final IStreamingServiceCallback callback) throws RemoteException {          final int uid = Binder.getCallingUid();          callback.asBinder().linkToDeath(new DeathRecipient() {              @Override @@ -162,19 +162,19 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {          return startStreaming(subscriptionId, serviceId, new StreamingServiceCallback() {              @Override -            public void onError(int errorCode, String message) { +            public void onError(final int errorCode, final String message) {                  try { -                    callback.error(errorCode, message); +                    callback.onError(errorCode, message);                  } catch (RemoteException e) {                      onAppCallbackDied(uid, subscriptionId);                  }              }              @Override -            public void onStreamStateUpdated(@StreamingService.StreamingState int state, -                    @StreamingService.StreamingStateChangeReason int reason) { +            public void onStreamStateUpdated(@StreamingService.StreamingState final int state, +                    @StreamingService.StreamingStateChangeReason final int reason) {                  try { -                    callback.streamStateUpdated(state, reason); +                    callback.onStreamStateUpdated(state, reason);                  } catch (RemoteException e) {                      onAppCallbackDied(uid, subscriptionId);                  } @@ -183,25 +183,25 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {              @Override              public void onMediaDescriptionUpdated() {                  try { -                    callback.mediaDescriptionUpdated(); +                    callback.onMediaDescriptionUpdated();                  } catch (RemoteException e) {                      onAppCallbackDied(uid, subscriptionId);                  }              }              @Override -            public void onBroadcastSignalStrengthUpdated(int signalStrength) { +            public void onBroadcastSignalStrengthUpdated(final int signalStrength) {                  try { -                    callback.broadcastSignalStrengthUpdated(signalStrength); +                    callback.onBroadcastSignalStrengthUpdated(signalStrength);                  } catch (RemoteException e) {                      onAppCallbackDied(uid, subscriptionId);                  }              }              @Override -            public void onStreamMethodUpdated(int methodType) { +            public void onStreamMethodUpdated(final int methodType) {                  try { -                    callback.streamMethodUpdated(methodType); +                    callback.onStreamMethodUpdated(methodType);                  } catch (RemoteException e) {                      onAppCallbackDied(uid, subscriptionId);                  } @@ -228,32 +228,19 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {      /**       * Stop streaming the stream identified by {@code serviceId}. Notification of the resulting       * stream state change should be reported to the app via -     * {@link IStreamingServiceCallback#streamStateUpdated(int, int)}. -     * -     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException} +     * {@link IStreamingServiceCallback#onStreamStateUpdated(int, int)}.       * -     * @param subscriptionId The subscription id to use. -     * @param serviceId The ID of the streaming service that the app wishes to stop. -     */ -    @Override -    public void stopStreaming(int subscriptionId, String serviceId) -            throws RemoteException { -    } - -    /** -     * Dispose of the stream identified by {@code serviceId} for the app identified by the -     * {@code appName} and {@code subscriptionId} arguments along with the caller's uid. -     * No notification back to the app is required for this operation, and the callback provided via +     * In addition, the callback provided via       * {@link #startStreaming(int, String, IStreamingServiceCallback)} should no longer be       * used after this method has called by the app.       *       * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}       *       * @param subscriptionId The subscription id to use. -     * @param serviceId The ID of the streaming service that the app wishes to dispose of. +     * @param serviceId The ID of the streaming service that the app wishes to stop.       */      @Override -    public void disposeStream(int subscriptionId, String serviceId) +    public void stopStreaming(int subscriptionId, String serviceId)              throws RemoteException {      } @@ -261,7 +248,7 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {       * Signals that the app wishes to dispose of the session identified by the       * {@code subscriptionId} argument and the caller's uid. No notification back to the       * app is required for this operation, and the corresponding callback provided via -     * {@link #initialize(IMbmsStreamingManagerCallback, int)} should no longer be used +     * {@link #initialize(IMbmsStreamingSessionCallback, int)} should no longer be used       * after this method has been called by the app.       *       * May throw an {@link IllegalStateException} diff --git a/telephony/java/android/telephony/mbms/vendor/VendorUtils.java b/telephony/java/android/telephony/mbms/vendor/VendorUtils.java index e66723771c02..7bab734397ee 100644 --- a/telephony/java/android/telephony/mbms/vendor/VendorUtils.java +++ b/telephony/java/android/telephony/mbms/vendor/VendorUtils.java @@ -22,7 +22,7 @@ import android.content.Context;  import android.content.Intent;  import android.content.pm.ResolveInfo;  import android.net.Uri; -import android.telephony.mbms.DownloadRequest; +import android.telephony.MbmsDownloadSession;  import android.telephony.mbms.MbmsDownloadReceiver;  import java.io.File; @@ -39,9 +39,9 @@ public class VendorUtils {      /**       * The MBMS middleware should send this when a download of single file has completed or       * failed. Mandatory extras are -     * {@link android.telephony.MbmsDownloadManager#EXTRA_MBMS_DOWNLOAD_RESULT} -     * {@link android.telephony.MbmsDownloadManager#EXTRA_MBMS_FILE_INFO} -     * {@link #EXTRA_REQUEST} +     * {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_RESULT} +     * {@link MbmsDownloadSession#EXTRA_MBMS_FILE_INFO} +     * {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_REQUEST}       * {@link #EXTRA_TEMP_LIST}       * {@link #EXTRA_FINAL_URI}       */ @@ -121,12 +121,6 @@ public class VendorUtils {              "android.telephony.mbms.extra.TEMP_FILES_IN_USE";      /** -     * Extra containing the {@link DownloadRequest} for which the download result or file -     * descriptor request is for. Must not be null. -     */ -    public static final String EXTRA_REQUEST = "android.telephony.mbms.extra.REQUEST"; - -    /**       * Extra containing a single {@link Uri} indicating the path to the temp file in which the       * decoded downloaded file resides. Must not be null.       */ diff --git a/tools/aapt/AaptXml.cpp b/tools/aapt/AaptXml.cpp index b04a55d91b9c..6801a4ec7325 100644 --- a/tools/aapt/AaptXml.cpp +++ b/tools/aapt/AaptXml.cpp @@ -99,24 +99,40 @@ String8 getResolvedAttribute(const ResTable& resTable, const ResXMLTree& tree,      if (idx < 0) {          return String8();      } +      Res_value value; -    if (tree.getAttributeValue(idx, &value) != NO_ERROR) { -        if (value.dataType == Res_value::TYPE_STRING) { -            size_t len; -            const char16_t* str = tree.getAttributeStringValue(idx, &len); -            return str ? String8(str, len) : String8(); +    if (tree.getAttributeValue(idx, &value) == BAD_TYPE) { +        if (outError != NULL) { +            *outError = "attribute value is corrupt";          } -        resTable.resolveReference(&value, 0); -        if (value.dataType != Res_value::TYPE_STRING) { -            if (outError != NULL) { -                *outError = "attribute is not a string value"; -            } -            return String8(); +        return String8(); +    } + +    // Check if the string is inline in the XML. +    if (value.dataType == Res_value::TYPE_STRING) { +        size_t len; +        const char16_t* str = tree.getAttributeStringValue(idx, &len); +        return str ? String8(str, len) : String8(); +    } + +    // Resolve the reference if there is one. +    ssize_t block = resTable.resolveReference(&value, 0); +    if (block < 0) { +        if (outError != NULL) { +            *outError = "attribute value reference does not exist"; +        } +        return String8(); +    } + +    if (value.dataType != Res_value::TYPE_STRING) { +        if (outError != NULL) { +            *outError = "attribute is not a string value";          } +        return String8();      } +      size_t len; -    const Res_value* value2 = &value; -    const char16_t* str = resTable.valueToString(value2, 0, NULL, &len); +    const char16_t* str = resTable.valueToString(&value, static_cast<size_t>(block), NULL, &len);      return str ? String8(str, len) : String8();  } diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index 14d05fdf6ee8..2ae2e496f25d 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -92,7 +92,9 @@ cc_library_host_static {          "flatten/XmlFlattener.cpp",          "io/BigBufferStreams.cpp",          "io/File.cpp", +        "io/FileInputStream.cpp",          "io/FileSystem.cpp", +        "io/StringInputStream.cpp",          "io/Util.cpp",          "io/ZipArchive.cpp",          "link/AutoVersioner.cpp", @@ -140,7 +142,8 @@ cc_library_host_static {          "xml/XmlDom.cpp",          "xml/XmlPullParser.cpp",          "xml/XmlUtil.cpp", -        "Format.proto", +        "Resources.proto", +        "ResourcesInternal.proto",      ],      proto: {          export_proto_headers: true, @@ -164,6 +167,7 @@ cc_library_host_shared {  cc_test_host {      name: "aapt2_tests",      srcs: [ +        "test/Builders.cpp",          "test/Common.cpp",          "**/*_test.cpp",      ], diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp index 7ff0c7227c9c..a9278c136cff 100644 --- a/tools/aapt2/ConfigDescription.cpp +++ b/tools/aapt2/ConfigDescription.cpp @@ -70,7 +70,7 @@ static bool parseMcc(const char* name, ResTable_config* out) {  static bool parseMnc(const char* name, ResTable_config* out) {    if (strcmp(name, kWildcardName) == 0) { -    if (out) out->mcc = 0; +    if (out) out->mnc = 0;      return true;    }    const char* c = name; @@ -967,8 +967,6 @@ bool ConfigDescription::ConflictsWith(const ConfigDescription& o) const {                 o.screenLayout & MASK_LAYOUTDIR) ||           !pred(screenLayout & MASK_SCREENLONG,                 o.screenLayout & MASK_SCREENLONG) || -         !pred(screenLayout & MASK_UI_MODE_TYPE, -               o.screenLayout & MASK_UI_MODE_TYPE) ||           !pred(uiMode & MASK_UI_MODE_TYPE, o.uiMode & MASK_UI_MODE_TYPE) ||           !pred(uiMode & MASK_UI_MODE_NIGHT, o.uiMode & MASK_UI_MODE_NIGHT) ||           !pred(screenLayout2 & MASK_SCREENROUND, diff --git a/tools/aapt2/ConfigDescription_test.cpp b/tools/aapt2/ConfigDescription_test.cpp index 14a565624e01..1f351bf7481d 100644 --- a/tools/aapt2/ConfigDescription_test.cpp +++ b/tools/aapt2/ConfigDescription_test.cpp @@ -140,4 +140,16 @@ TEST(ConfigDescriptionTest, ParseVrAttribute) {    EXPECT_EQ(std::string("vrheadset-v26"), config.toString().string());  } +TEST(ConfigDescriptionTest, RangeQualifiersDoNotConflict) { +  using test::ParseConfigOrDie; + +  EXPECT_FALSE(ParseConfigOrDie("large").ConflictsWith(ParseConfigOrDie("normal-land"))); +  EXPECT_FALSE(ParseConfigOrDie("long-hdpi").ConflictsWith(ParseConfigOrDie("xhdpi"))); +  EXPECT_FALSE(ParseConfigOrDie("sw600dp").ConflictsWith(ParseConfigOrDie("sw700dp"))); +  EXPECT_FALSE(ParseConfigOrDie("v11").ConflictsWith(ParseConfigOrDie("v21"))); +  EXPECT_FALSE(ParseConfigOrDie("h600dp").ConflictsWith(ParseConfigOrDie("h300dp"))); +  EXPECT_FALSE(ParseConfigOrDie("w400dp").ConflictsWith(ParseConfigOrDie("w300dp"))); +  EXPECT_FALSE(ParseConfigOrDie("600x400").ConflictsWith(ParseConfigOrDie("300x200"))); +} +  }  // namespace aapt diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index b872ebbeb159..49ed7780f950 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -33,6 +33,8 @@  namespace aapt { +namespace { +  class PrintVisitor : public ValueVisitor {   public:    using ValueVisitor::Visit; @@ -88,9 +90,13 @@ class PrintVisitor : public ValueVisitor {      }    } -  void Visit(Array* array) override { array->Print(&std::cout); } +  void Visit(Array* array) override { +    array->Print(&std::cout); +  } -  void Visit(Plural* plural) override { plural->Print(&std::cout); } +  void Visit(Plural* plural) override { +    plural->Print(&std::cout); +  }    void Visit(Styleable* styleable) override {      std::cout << "(styleable)"; @@ -110,11 +116,14 @@ class PrintVisitor : public ValueVisitor {      }    } -  void VisitItem(Item* item) override { item->Print(&std::cout); } +  void VisitItem(Item* item) override { +    item->Print(&std::cout); +  }  }; -void Debug::PrintTable(ResourceTable* table, -                       const DebugPrintTableOptions& options) { +}  // namespace + +void Debug::PrintTable(ResourceTable* table, const DebugPrintTableOptions& options) {    PrintVisitor visitor;    for (auto& package : table->packages) { @@ -148,10 +157,9 @@ void Debug::PrintTable(ResourceTable* table,        }        for (const ResourceEntry* entry : sorted_entries) { -        ResourceId id(package->id ? package->id.value() : uint8_t(0), -                      type->id ? type->id.value() : uint8_t(0), -                      entry->id ? entry->id.value() : uint16_t(0)); -        ResourceName name(package->name, type->type, entry->name); +        const ResourceId id(package->id.value_or_default(0), type->id.value_or_default(0), +                            entry->id.value_or_default(0)); +        const ResourceName name(package->name, type->type, entry->name);          std::cout << "    spec resource " << id << " " << name;          switch (entry->symbol_status.state) { @@ -180,16 +188,14 @@ void Debug::PrintTable(ResourceTable* table,    }  } -static size_t GetNodeIndex(const std::vector<ResourceName>& names, -                           const ResourceName& name) { +static size_t GetNodeIndex(const std::vector<ResourceName>& names, const ResourceName& name) {    auto iter = std::lower_bound(names.begin(), names.end(), name);    CHECK(iter != names.end());    CHECK(*iter == name);    return std::distance(names.begin(), iter);  } -void Debug::PrintStyleGraph(ResourceTable* table, -                            const ResourceName& target_style) { +void Debug::PrintStyleGraph(ResourceTable* table, const ResourceName& target_style) {    std::map<ResourceName, std::set<ResourceName>> graph;    std::queue<ResourceName> styles_to_visit; @@ -223,8 +229,7 @@ void Debug::PrintStyleGraph(ResourceTable* table,    std::cout << "digraph styles {\n";    for (const auto& name : names) { -    std::cout << "  node_" << GetNodeIndex(names, name) << " [label=\"" << name -              << "\"];\n"; +    std::cout << "  node_" << GetNodeIndex(names, name) << " [label=\"" << name << "\"];\n";    }    for (const auto& entry : graph) { @@ -243,8 +248,7 @@ void Debug::PrintStyleGraph(ResourceTable* table,  void Debug::DumpHex(const void* data, size_t len) {    const uint8_t* d = (const uint8_t*)data;    for (size_t i = 0; i < len; i++) { -    std::cerr << std::hex << std::setfill('0') << std::setw(2) << (uint32_t)d[i] -              << " "; +    std::cerr << std::hex << std::setfill('0') << std::setw(2) << (uint32_t)d[i] << " ";      if (i % 8 == 7) {        std::cerr << "\n";      } @@ -262,8 +266,15 @@ class XmlPrinter : public xml::Visitor {    using xml::Visitor::Visit;    void Visit(xml::Element* el) override { -    std::cerr << prefix_; -    std::cerr << "E: "; +    const size_t previous_size = prefix_.size(); + +    for (const xml::NamespaceDecl& decl : el->namespace_decls) { +      std::cerr << prefix_ << "N: " << decl.prefix << "=" << decl.uri +                << " (line=" << decl.line_number << ")\n"; +      prefix_ += "  "; +    } + +    std::cerr << prefix_ << "E: ";      if (!el->namespace_uri.empty()) {        std::cerr << el->namespace_uri << ":";      } @@ -283,26 +294,13 @@ class XmlPrinter : public xml::Visitor {        std::cerr << "=" << attr.value << "\n";      } -    const size_t previous_size = prefix_.size();      prefix_ += "  ";      xml::Visitor::Visit(el);      prefix_.resize(previous_size);    } -  void Visit(xml::Namespace* ns) override { -    std::cerr << prefix_; -    std::cerr << "N: " << ns->namespace_prefix << "=" << ns->namespace_uri -              << " (line=" << ns->line_number << ")\n"; - -    const size_t previous_size = prefix_.size(); -    prefix_ += "  "; -    xml::Visitor::Visit(ns); -    prefix_.resize(previous_size); -  } -    void Visit(xml::Text* text) override { -    std::cerr << prefix_; -    std::cerr << "T: '" << text->text << "'\n"; +    std::cerr << prefix_ << "T: '" << text->text << "'\n";    }   private: diff --git a/tools/aapt2/Format.proto b/tools/aapt2/Format.proto deleted file mode 100644 index 870b735f70a7..000000000000 --- a/tools/aapt2/Format.proto +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - *      http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -syntax = "proto2"; - -option optimize_for = LITE_RUNTIME; - -package aapt.pb; - -message ConfigDescription { -	optional bytes data = 1; -	optional string product = 2; -} - -message StringPool { -	optional bytes data = 1; -} - -message CompiledFile { -	message Symbol { -		optional string resource_name = 1; -		optional uint32 line_no = 2; -	} - -	optional string resource_name = 1; -	optional ConfigDescription config = 2; -	optional string source_path = 3; -	repeated Symbol exported_symbols = 4; -} - -message ResourceTable { -	optional StringPool string_pool = 1; -	optional StringPool source_pool = 2; -	optional StringPool symbol_pool = 3; -	repeated Package packages = 4; -} - -message Package { -	optional uint32 package_id = 1; -	optional string package_name = 2; -	repeated Type types = 3; -} - -message Type {	 -	optional uint32 id = 1; -	optional string name = 2; -	repeated Entry entries = 3; -} - -message SymbolStatus { -	enum Visibility { -		Unknown = 0; -		Private = 1; -		Public = 2; -	} -	optional Visibility visibility = 1; -	optional Source source = 2; -	optional string comment = 3; -	optional bool allow_new = 4; -} - -message Entry { -	optional uint32 id = 1; -	optional string name = 2; -	optional SymbolStatus symbol_status = 3; -	repeated ConfigValue config_values = 4; -} - -message ConfigValue { -	optional ConfigDescription config = 1; -	optional Value value = 2; -} - -message Source { -	optional uint32 path_idx = 1; -	optional uint32 line_no = 2; -	optional uint32 col_no = 3; -} - -message Reference { -	enum Type { -		Ref = 0; -		Attr = 1; -	} -	optional Type type = 1; -	optional uint32 id = 2; -	optional uint32 symbol_idx = 3; -	optional bool private = 4; -} - -message Id { -} - -message String { -	optional uint32 idx = 1; -} - -message RawString { -	optional uint32 idx = 1; -} - -message FileReference { -	optional uint32 path_idx = 1; -} - -message Primitive { -	optional uint32 type = 1; -	optional uint32 data = 2; -} - -message Attribute { -	message Symbol { -		optional Source source = 1; -		optional string comment = 2; -		optional Reference name = 3; -		optional uint32 value = 4; -	} -	optional uint32 format_flags = 1; -	optional int32 min_int = 2; -	optional int32 max_int = 3; -	repeated Symbol symbols = 4; -} - -message Style { -	message Entry { -		optional Source source = 1; -		optional string comment = 2; -		optional Reference key = 3; -		optional Item item = 4; -	} - -	optional Reference parent = 1; -	optional Source parent_source = 2; -	repeated Entry entries = 3; -} - -message Styleable { -	message Entry { -		optional Source source = 1; -		optional string comment = 2; -		optional Reference attr = 3; -	} -	repeated Entry entries = 1; -} - -message Array { -	message Entry { -		optional Source source = 1; -		optional string comment = 2; -		optional Item item = 3; -	} -	repeated Entry entries = 1; -} - -message Plural { -	enum Arity { -		Zero = 0; -		One = 1; -		Two = 2; -		Few = 3; -		Many = 4; -		Other = 5; -	} -		 -	message Entry { -		optional Source source = 1; -		optional string comment = 2; -		optional Arity arity = 3; -		optional Item item = 4; -	} -	repeated Entry entries = 1; -} - -message Item { -	optional Reference ref = 1; -	optional String str = 2; -	optional RawString raw_str = 3; -	optional FileReference file = 4; -	optional Id id = 5; -	optional Primitive prim = 6; -} - -message CompoundValue { -	optional Attribute attr = 1; -	optional Style style = 2; -	optional Styleable styleable = 3; -	optional Array array = 4; -	optional Plural plural = 5; -} - -message Value { -	optional Source source = 1; -	optional string comment = 2; -	optional bool weak = 3; -	 -	optional Item item = 4; -	optional CompoundValue compound_value = 5;	 -} diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index c5d38abcdf71..36ab30c7fb6e 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -14,12 +14,26 @@   * limitations under the License.   */ +#ifdef _WIN32 +// clang-format off +#include <windows.h> +#include <shellapi.h> +// clang-format on +#endif +  #include <iostream>  #include <vector> +#include "android-base/stringprintf.h" +#include "android-base/utf8.h"  #include "androidfw/StringPiece.h"  #include "Diagnostics.h" +#include "util/Files.h" +#include "util/Util.h" + +using ::android::StringPiece; +using ::android::base::StringPrintf;  namespace aapt { @@ -27,54 +41,136 @@ namespace aapt {  static const char* sMajorVersion = "2";  // Update minor version whenever a feature or flag is added. -static const char* sMinorVersion = "18"; +static const char* sMinorVersion = "19"; -int PrintVersion() { -  std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "." -            << sMinorVersion << std::endl; -  return 0; +static void PrintVersion() { +  std::cerr << StringPrintf("Android Asset Packaging Tool (aapt) %s:%s", sMajorVersion, +                            sMinorVersion) +            << std::endl; +} + +static void PrintUsage() { +  std::cerr << "\nusage: aapt2 [compile|link|dump|diff|optimize|version] ..." << std::endl;  } -extern int Compile(const std::vector<android::StringPiece>& args, IDiagnostics* diagnostics); -extern int Link(const std::vector<android::StringPiece>& args, IDiagnostics* diagnostics); -extern int Dump(const std::vector<android::StringPiece>& args); -extern int Diff(const std::vector<android::StringPiece>& args); -extern int Optimize(const std::vector<android::StringPiece>& args); +extern int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics); +extern int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics); +extern int Dump(const std::vector<StringPiece>& args); +extern int Diff(const std::vector<StringPiece>& args); +extern int Optimize(const std::vector<StringPiece>& args); -}  // namespace aapt +static int ExecuteCommand(const StringPiece& command, const std::vector<StringPiece>& args, +                          IDiagnostics* diagnostics) { +  if (command == "compile" || command == "c") { +    return Compile(args, diagnostics); +  } else if (command == "link" || command == "l") { +    return Link(args, diagnostics); +  } else if (command == "dump" || command == "d") { +    return Dump(args); +  } else if (command == "diff") { +    return Diff(args); +  } else if (command == "optimize") { +    return Optimize(args); +  } else if (command == "version") { +    PrintVersion(); +    return 0; +  } +  diagnostics->Error(DiagMessage() << "unknown command '" << command << "'"); +  return -1; +} -int main(int argc, char** argv) { -  if (argc >= 2) { -    argv += 1; -    argc -= 1; +static void RunDaemon(IDiagnostics* diagnostics) { +  std::cout << "Ready" << std::endl; -    std::vector<android::StringPiece> args; -    for (int i = 1; i < argc; i++) { -      args.push_back(argv[i]); +  // Run in daemon mode. The first line of input is the command. This can be 'quit' which ends +  // the daemon mode. Each subsequent line is a single parameter to the command. The end of a +  // invocation is signaled by providing an empty line. At any point, an EOF signal or the +  // command 'quit' will end the daemon mode. +  while (true) { +    std::vector<std::string> raw_args; +    for (std::string line; std::getline(std::cin, line) && !line.empty();) { +      raw_args.push_back(line);      } -    android::StringPiece command(argv[0]); -    if (command == "compile" || command == "c") { -      aapt::StdErrDiagnostics diagnostics; -      return aapt::Compile(args, &diagnostics); -    } else if (command == "link" || command == "l") { -      aapt::StdErrDiagnostics diagnostics; -      return aapt::Link(args, &diagnostics); -    } else if (command == "dump" || command == "d") { -      return aapt::Dump(args); -    } else if (command == "diff") { -      return aapt::Diff(args); -    } else if (command == "optimize") { -      return aapt::Optimize(args); -    } else if (command == "version") { -      return aapt::PrintVersion(); +    if (!std::cin) { +      break;      } -    std::cerr << "unknown command '" << command << "'\n"; -  } else { + +    // An empty command does nothing. +    if (raw_args.empty()) { +      continue; +    } + +    if (raw_args[0] == "quit") { +      break; +    } + +    std::vector<StringPiece> args; +    args.insert(args.end(), ++raw_args.begin(), raw_args.end()); +    int ret = ExecuteCommand(raw_args[0], args, diagnostics); +    if (ret != 0) { +      std::cerr << "Error" << std::endl; +    } +    std::cerr << "Done" << std::endl; +  } +  std::cout << "Exiting daemon" << std::endl; +} + +}  // namespace aapt + +int MainImpl(int argc, char** argv) { +  if (argc < 2) {      std::cerr << "no command specified\n"; +    aapt::PrintUsage(); +    return -1;    } -  std::cerr << "\nusage: aapt2 [compile|link|dump|diff|optimize|version] ..." -            << std::endl; -  return 1; +  argv += 1; +  argc -= 1; + +  aapt::StdErrDiagnostics diagnostics; + +  // Collect the arguments starting after the program name and command name. +  std::vector<StringPiece> args; +  for (int i = 1; i < argc; i++) { +    args.push_back(argv[i]); +  } + +  const StringPiece command(argv[0]); +  if (command != "daemon" && command != "m") { +    // Single execution. +    const int result = aapt::ExecuteCommand(command, args, &diagnostics); +    if (result < 0) { +      aapt::PrintUsage(); +    } +    return result; +  } + +  aapt::RunDaemon(&diagnostics); +  return 0; +} + +int main(int argc, char** argv) { +#ifdef _WIN32 +  LPWSTR* wide_argv = CommandLineToArgvW(GetCommandLineW(), &argc); +  CHECK(wide_argv != nullptr) << "invalid command line parameters passed to process"; + +  std::vector<std::string> utf8_args; +  for (int i = 0; i < argc; i++) { +    std::string utf8_arg; +    if (!::android::base::WideToUTF8(wide_argv[i], &utf8_arg)) { +      std::cerr << "error converting input arguments to UTF-8" << std::endl; +      return 1; +    } +    utf8_args.push_back(std::move(utf8_arg)); +  } +  LocalFree(wide_argv); + +  std::unique_ptr<char* []> utf8_argv(new char*[utf8_args.size()]); +  for (int i = 0; i < argc; i++) { +    utf8_argv[i] = const_cast<char*>(utf8_args[i].c_str()); +  } +  argv = utf8_argv.get(); +#endif +  return MainImpl(argc, argv);  } diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp index 35971e7bd99b..a9f5f298e019 100644 --- a/tools/aapt2/Resource.cpp +++ b/tools/aapt2/Resource.cpp @@ -61,6 +61,8 @@ StringPiece ToString(ResourceType type) {        return "menu";      case ResourceType::kMipmap:        return "mipmap"; +    case ResourceType::kNavigation: +      return "navigation";      case ResourceType::kPlurals:        return "plurals";      case ResourceType::kRaw: @@ -98,6 +100,7 @@ static const std::map<StringPiece, ResourceType> sResourceTypeMap{      {"layout", ResourceType::kLayout},      {"menu", ResourceType::kMenu},      {"mipmap", ResourceType::kMipmap}, +    {"navigation", ResourceType::kNavigation},      {"plurals", ResourceType::kPlurals},      {"raw", ResourceType::kRaw},      {"string", ResourceType::kString}, diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index 0a74c1a0f77d..cbcc8fb805aa 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -59,6 +59,7 @@ enum class ResourceType {    kLayout,    kMenu,    kMipmap, +  kNavigation,    kPlurals,    kRaw,    kString, diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index a5783a532e23..1c3ac2ad4f17 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -1219,7 +1219,7 @@ bool ResourceParser::ParseArrayImpl(xml::XmlPullParser* parser,          continue;        }        item->SetSource(item_source); -      array->items.emplace_back(std::move(item)); +      array->elements.emplace_back(std::move(item));      } else if (!ShouldIgnoreElement(element_namespace, element_name)) {        diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 1683c64a6a5c..144ebd22e105 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -22,9 +22,11 @@  #include "ResourceTable.h"  #include "ResourceUtils.h"  #include "ResourceValues.h" +#include "io/StringInputStream.h"  #include "test/Test.h"  #include "xml/XmlPullParser.h" +using ::aapt::io::StringInputStream;  using ::aapt::test::StrValueEq;  using ::aapt::test::ValueEq;  using ::android::ResTable_map; @@ -43,11 +45,13 @@ constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>  TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) {    std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); -  std::stringstream input(kXmlPreamble); -  input << R"(<attr name="foo"/>)" << std::endl;    ResourceTable table;    ResourceParser parser(context->GetDiagnostics(), &table, Source{"test"}, {}); -  xml::XmlPullParser xml_parser(input); + +  std::string input = kXmlPreamble; +  input += R"(<attr name="foo"/>)"; +  StringInputStream in(input); +  xml::XmlPullParser xml_parser(&in);    ASSERT_FALSE(parser.Parse(&xml_parser));  } @@ -62,12 +66,16 @@ class ResourceParserTest : public ::testing::Test {    }    ::testing::AssertionResult TestParse(const StringPiece& str, const ConfigDescription& config) { -    std::stringstream input(kXmlPreamble); -    input << "<resources>\n" << str << "\n</resources>" << std::endl;      ResourceParserOptions parserOptions;      ResourceParser parser(context_->GetDiagnostics(), &table_, Source{"test"}, config,                            parserOptions); -    xml::XmlPullParser xmlParser(input); + +    std::string input = kXmlPreamble; +    input += "<resources>\n"; +    input.append(str.data(), str.size()); +    input += "\n</resources>"; +    StringInputStream in(input); +    xml::XmlPullParser xmlParser(&in);      if (parser.Parse(&xmlParser)) {        return ::testing::AssertionSuccess();      } @@ -532,11 +540,11 @@ TEST_F(ResourceParserTest, ParseArray) {    Array* array = test::GetValue<Array>(&table_, "array/foo");    ASSERT_THAT(array, NotNull()); -  ASSERT_THAT(array->items, SizeIs(3)); +  ASSERT_THAT(array->elements, SizeIs(3)); -  EXPECT_THAT(ValueCast<Reference>(array->items[0].get()), NotNull()); -  EXPECT_THAT(ValueCast<String>(array->items[1].get()), NotNull()); -  EXPECT_THAT(ValueCast<BinaryPrimitive>(array->items[2].get()), NotNull()); +  EXPECT_THAT(ValueCast<Reference>(array->elements[0].get()), NotNull()); +  EXPECT_THAT(ValueCast<String>(array->elements[1].get()), NotNull()); +  EXPECT_THAT(ValueCast<BinaryPrimitive>(array->elements[2].get()), NotNull());  }  TEST_F(ResourceParserTest, ParseStringArray) { @@ -557,9 +565,9 @@ TEST_F(ResourceParserTest, ParseArrayWithFormat) {    Array* array = test::GetValue<Array>(&table_, "array/foo");    ASSERT_THAT(array, NotNull()); -  ASSERT_THAT(array->items, SizeIs(1)); +  ASSERT_THAT(array->elements, SizeIs(1)); -  String* str = ValueCast<String>(array->items[0].get()); +  String* str = ValueCast<String>(array->elements[0].get());    ASSERT_THAT(str, NotNull());    EXPECT_THAT(*str, StrValueEq("100"));  } diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index eb59175edf3b..1cba19462839 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -805,13 +805,12 @@ bool Array::Equals(const Value* value) const {      return false;    } -  if (items.size() != other->items.size()) { +  if (elements.size() != other->elements.size()) {      return false;    } -  return std::equal(items.begin(), items.end(), other->items.begin(), -                    [](const std::unique_ptr<Item>& a, -                       const std::unique_ptr<Item>& b) -> bool { +  return std::equal(elements.begin(), elements.end(), other->elements.begin(), +                    [](const std::unique_ptr<Item>& a, const std::unique_ptr<Item>& b) -> bool {                        return a->Equals(b.get());                      });  } @@ -820,14 +819,14 @@ Array* Array::Clone(StringPool* new_pool) const {    Array* array = new Array();    array->comment_ = comment_;    array->source_ = source_; -  for (auto& item : items) { -    array->items.emplace_back(std::unique_ptr<Item>(item->Clone(new_pool))); +  for (auto& item : elements) { +    array->elements.emplace_back(std::unique_ptr<Item>(item->Clone(new_pool)));    }    return array;  }  void Array::Print(std::ostream* out) const { -  *out << "(array) [" << util::Joiner(items, ", ") << "]"; +  *out << "(array) [" << util::Joiner(elements, ", ") << "]";  }  bool Plural::Equals(const Value* value) const { diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index 7e7547fc1b94..275864bbcd3e 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -292,7 +292,7 @@ struct Style : public BaseValue<Style> {  };  struct Array : public BaseValue<Array> { -  std::vector<std::unique_ptr<Item>> items; +  std::vector<std::unique_ptr<Item>> elements;    bool Equals(const Value* value) const override;    Array* Clone(StringPool* new_pool) const override; diff --git a/tools/aapt2/ResourceValues_test.cpp b/tools/aapt2/ResourceValues_test.cpp index 06c3404561de..10f9b55ede08 100644 --- a/tools/aapt2/ResourceValues_test.cpp +++ b/tools/aapt2/ResourceValues_test.cpp @@ -54,19 +54,19 @@ TEST(ResourceValuesTest, ArrayEquals) {    StringPool pool;    Array a; -  a.items.push_back(util::make_unique<String>(pool.MakeRef("one"))); -  a.items.push_back(util::make_unique<String>(pool.MakeRef("two"))); +  a.elements.push_back(util::make_unique<String>(pool.MakeRef("one"))); +  a.elements.push_back(util::make_unique<String>(pool.MakeRef("two")));    Array b; -  b.items.push_back(util::make_unique<String>(pool.MakeRef("une"))); -  b.items.push_back(util::make_unique<String>(pool.MakeRef("deux"))); +  b.elements.push_back(util::make_unique<String>(pool.MakeRef("une"))); +  b.elements.push_back(util::make_unique<String>(pool.MakeRef("deux")));    Array c; -  c.items.push_back(util::make_unique<String>(pool.MakeRef("uno"))); +  c.elements.push_back(util::make_unique<String>(pool.MakeRef("uno")));    Array d; -  d.items.push_back(util::make_unique<String>(pool.MakeRef("one"))); -  d.items.push_back(util::make_unique<String>(pool.MakeRef("two"))); +  d.elements.push_back(util::make_unique<String>(pool.MakeRef("one"))); +  d.elements.push_back(util::make_unique<String>(pool.MakeRef("two")));    EXPECT_FALSE(a.Equals(&b));    EXPECT_FALSE(a.Equals(&c)); @@ -78,8 +78,8 @@ TEST(ResourceValuesTest, ArrayClone) {    StringPool pool;    Array a; -  a.items.push_back(util::make_unique<String>(pool.MakeRef("one"))); -  a.items.push_back(util::make_unique<String>(pool.MakeRef("two"))); +  a.elements.push_back(util::make_unique<String>(pool.MakeRef("one"))); +  a.elements.push_back(util::make_unique<String>(pool.MakeRef("two")));    std::unique_ptr<Array> b(a.Clone(&pool));    EXPECT_TRUE(a.Equals(b.get())); diff --git a/tools/aapt2/Resource_test.cpp b/tools/aapt2/Resource_test.cpp index ad4e3ce02b32..c557f3c77654 100644 --- a/tools/aapt2/Resource_test.cpp +++ b/tools/aapt2/Resource_test.cpp @@ -93,6 +93,10 @@ TEST(ResourceTypeTest, ParseResourceTypes) {    ASSERT_NE(type, nullptr);    EXPECT_EQ(*type, ResourceType::kMipmap); +  type = ParseResourceType("navigation"); +  ASSERT_NE(type, nullptr); +  EXPECT_EQ(*type, ResourceType::kNavigation); +    type = ParseResourceType("plurals");    ASSERT_NE(type, nullptr);    EXPECT_EQ(*type, ResourceType::kPlurals); diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto new file mode 100644 index 000000000000..71f33b0853ad --- /dev/null +++ b/tools/aapt2/Resources.proto @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Keep proto2 syntax because we require the distinction between fields that +// are set and unset. +syntax = "proto2"; + +option java_package = "com.android.aapt"; +option optimize_for = LITE_RUNTIME; + +package aapt.pb; + +// A configuration description that wraps the binary form of the C++ class +// aapt::ConfigDescription, with an added product definition. +// TODO(adamlesinski): Flesh this out to be represented in proto. +message ConfigDescription { +  optional bytes data = 1; +  optional string product = 2; +} + +// A string pool that wraps the binary form of the C++ class android::ResStringPool. +message StringPool { +  optional bytes data = 1; +} + +// The position of a declared entity within a file. +message SourcePosition { +  optional uint32 line_number = 1; +  optional uint32 column_number = 2; +} + +// Developer friendly source file information for an entity in the resource table. +message Source { +  // The index of the string path within the source string pool of a ResourceTable. +  optional uint32 path_idx = 1; +  optional SourcePosition position = 2; +} + +// Top level message representing a resource table. +message ResourceTable { +  // The string pool containing source paths referenced throughout the resource table. This does +  // not end up in the final binary ARSC file. +  optional StringPool source_pool = 1; + +  // Resource definitions corresponding to an Android package. +  repeated Package package = 2; +} + +// Defines resources for an Android package. +message Package { +  // The package ID of this package, in the range [0x00, 0xff]. +  // The ID 0x00 is reserved for shared libraries, or when the ID is assigned at run-time. +  // The ID 0x01 is reserved for the 'android' package (framework). +  // The ID range [0x02, 0x7f) is reserved for auto-assignment to shared libraries at run-time. +  // The ID 0x7f is reserved for the application package. +  // IDs > 0x7f are reserved for the application as well and are treated as feature splits. +  optional uint32 package_id = 1; + +  // The Java compatible Android package name of the app. +  optional string package_name = 2; + +  // The series of types defined by the package. +  repeated Type type = 3; +} + +// A set of resources grouped under a common type. Such types include string, layout, xml, dimen, +// attr, etc. This maps to the second part of a resource identifier in Java (R.type.entry). +message Type { +  // The ID of the type. This may be 0, which indicates no ID is set. +  optional uint32 id = 1; + +  // The name of the type. This corresponds to the 'type' part of a full resource name of the form +  // package:type/entry. The set of legal type names is listed in Resource.cpp. +  optional string name = 2; + +  // The entries defined for this type. +  repeated Entry entry = 3; +} + +// The status of a symbol/entry. This contains information like visibility (public/private), +// comments, and whether the entry can be overridden. +message SymbolStatus { +  // The visibility of the resource outside of its package. +  enum Visibility { +    // No visibility was explicitly specified. This is typically treated as private. +    // The distinction is important when two separate R.java files are generated: a public and +    // private one. An unknown visibility, in this case, would cause the resource to be omitted +    // from either R.java. +    UNKNOWN = 0; + +    // A resource was explicitly marked as private. This means the resource can not be accessed +    // outside of its package unless the @*package:type/entry notation is used (the asterisk being +    // the private accessor). If two R.java files are generated (private + public), the resource +    // will only be emitted to the private R.java file. +    PRIVATE = 1; + +    // A resource was explicitly marked as public. This means the resource can be accessed +    // from any package, and is emitted into all R.java files, public and private. +    PUBLIC = 2; +  } + +  optional Visibility visibility = 1; + +  // The path at which this entry's visibility was defined (eg. public.xml). +  optional Source source = 2; + +  // The comment associated with the <public> tag. +  optional string comment = 3; + +  // Whether the symbol can be merged into another resource table without there being an existing +  // definition to override. Used for overlays and set to true when <add-resource> is specified. +  optional bool allow_new = 4; +} + +// An entry declaration. An entry has a full resource ID that is the combination of package ID, +// type ID, and its own entry ID. An entry on its own has no value, but values are defined for +// various configurations/variants. +message Entry { +  // The ID of this entry. Together with the package ID and type ID, this forms a full resource ID +  // of the form 0xPPTTEEEE, where PP is the package ID, TT is the type ID, and EEEE is the entry +  // ID. +  optional uint32 id = 1; + +  // The name of this entry. This corresponds to the 'entry' part of a full resource name of the +  // form package:type/entry. +  optional string name = 2; + +  // The symbol status of this entry, which includes visibility information. +  optional SymbolStatus symbol_status = 3; + +  // The set of values defined for this entry, each corresponding to a different +  // configuration/variant. +  repeated ConfigValue config_value = 4; +} + +// A Configuration/Value pair. +message ConfigValue { +  optional ConfigDescription config = 1; +  optional Value value = 2; +} + +// The generic meta-data for every value in a resource table. +message Value { +  // Where the value was defined. +  optional Source source = 1; + +  // Any comment associated with the value. +  optional string comment = 2; + +  // Whether the value can be overridden. +  optional bool weak = 3; + +  // If the value is an Item, this is set. +  optional Item item = 4; + +  // If the value is a CompoundValue, this is set. +  optional CompoundValue compound_value = 5; +} + +// An Item is an abstract type. It represents a value that can appear inline in many places, such +// as XML attribute values or on the right hand side of style attribute definitions. The concrete +// type is one of the types below. Only one can be set. +message Item { +  optional Reference ref = 1; +  optional String str = 2; +  optional RawString raw_str = 3; +  optional StyledString styled_str = 4; +  optional FileReference file = 5; +  optional Id id = 6; +  optional Primitive prim = 7; +} + +// A CompoundValue is an abstract type. It represents a value that is a made of other values. +// These can only usually appear as top-level resources. The concrete type is one of the types +// below. Only one can be set. +message CompoundValue { +  optional Attribute attr = 1; +  optional Style style = 2; +  optional Styleable styleable = 3; +  optional Array array = 4; +  optional Plural plural = 5; +} + +// A value that is a reference to another resource. This reference can be by name or resource ID. +message Reference { +  enum Type { +    // A plain reference (@package:type/entry). +    REFERENCE = 0; + +    // A reference to a theme attribute (?package:type/entry). +    ATTRIBUTE = 1; +  } + +  optional Type type = 1; + +  // The resource ID (0xPPTTEEEE) of the resource being referred. +  optional uint32 id = 2; + +  // The optional resource name. +  optional string name = 3; + +  // Whether this reference is referencing a private resource (@*package:type/entry). +  optional bool private = 4; +} + +// A value that represents an ID. This is just a placeholder, as ID values are used to occupy a +// resource ID (0xPPTTEEEE) as a unique identifier. Their value is unimportant. +message Id { +} + +// A value that is a string. +message String { +  optional string value = 1; +} + +// A value that is a raw string, which is unescaped/uninterpreted. This is typically used to +// represent the value of a style attribute before the attribute is compiled and the set of +// allowed values is known. +message RawString { +  optional string value = 1; +} + +// A string with styling information, like html tags that specify boldness, italics, etc. +message StyledString { +  // The raw text of the string. +  optional string value = 1; + +  // A Span marks a region of the string text that is styled. +  message Span { +    // The name of the tag, and its attributes, encoded as follows: +    // tag_name;attr1=value1;attr2=value2;[...] +    optional string tag = 1; + +    // The first character position this span applies to, in UTF-16 offset. +    optional uint32 first_char = 2; + +    // The last character position this span applies to, in UTF-16 offset. +    optional uint32 last_char = 3; +  } + +  repeated Span span = 2; +} + +// A value that is a reference to an external entity, like an XML file or a PNG. +message FileReference { +  // Path to a file within the APK (typically res/type-config/entry.ext). +  optional string path = 1; +} + +// A value that represents a primitive data type (float, int, boolean, etc.). +// Corresponds to the fields (type/data) of the C struct android::Res_value. +message Primitive { +  optional uint32 type = 1; +  optional uint32 data = 2; +} + +// A value that represents an XML attribute and what values it accepts. +message Attribute { +  // A Symbol used to represent an enum or a flag. +  message Symbol { +    // Where the enum/flag item was defined. +    optional Source source = 1; + +    // Any comments associated with the enum or flag. +    optional string comment = 2; + +    // The name of the enum/flag as a reference. Enums/flag items are generated as ID resource +    // values. +    optional Reference name = 3; + +    // The value of the enum/flag. +    optional uint32 value = 4; +  } + +  // Bitmask of formats allowed for an attribute. +  enum FormatFlags { +    ANY = 0x0000ffff;    // Allows any type except ENUM and FLAGS. +    REFERENCE = 0x01;    // Allows Reference values. +    STRING = 0x02;       // Allows String/StyledString values. +    INTEGER = 0x04;      // Allows any integer BinaryPrimitive values. +    BOOLEAN = 0x08;      // Allows any boolean BinaryPrimitive values. +    COLOR = 0x010;       // Allows any color BinaryPrimitive values. +    FLOAT = 0x020;       // Allows any float BinaryPrimitive values. +    DIMENSION = 0x040;   // Allows any dimension BinaryPrimitive values. +    FRACTION = 0x080;    // Allows any fraction BinaryPrimitive values. +    ENUM = 0x00010000;   // Allows enums that are defined in the Attribute's symbols. +                         // ENUM and FLAGS cannot BOTH be set. +    FLAGS = 0x00020000;  // Allows flags that are defined in the Attribute's symbols. +                         // ENUM and FLAGS cannot BOTH be set. +  } + +  // A bitmask of types that this XML attribute accepts. Corresponds to the flags in the +  // enum FormatFlags. +  optional uint32 format_flags = 1; + +  // The smallest integer allowed for this XML attribute. Only makes sense if the format includes +  // FormatFlags::INTEGER. +  optional int32 min_int = 2; + +  // The largest integer allowed for this XML attribute. Only makes sense if the format includes +  // FormatFlags::INTEGER. +  optional int32 max_int = 3; + +  // The set of enums/flags defined in this attribute. Only makes sense if the format includes +  // either FormatFlags::ENUM or FormatFlags::FLAGS. Having both is an error. +  repeated Symbol symbol = 4; +} + +// A value that represents a style. +message Style { +  // An XML attribute/value pair defined in the style. +  message Entry { +    // Where the entry was defined. +    optional Source source = 1; + +    // Any comments associated with the entry. +    optional string comment = 2; + +    // A reference to the XML attribute. +    optional Reference key = 3; + +    // The Item defined for this XML attribute. +    optional Item item = 4; +  } + +  // The optinal style from which this style inherits attributes. +  optional Reference parent = 1; + +  // The source file information of the parent inheritance declaration. +  optional Source parent_source = 2; + +  // The set of XML attribute/value pairs for this style. +  repeated Entry entry = 3; +} + +// A value that represents a <declare-styleable> XML resource. These are not real resources and +// only end up as Java fields in the generated R.java. They do not end up in the binary ARSC file. +message Styleable { +  // An attribute defined for this styleable. +  message Entry { +    // Where the attribute was defined within the <declare-styleable> block. +    optional Source source = 1; + +    // Any comments associated with the declaration. +    optional string comment = 2; + +    // The reference to the attribute. +    optional Reference attr = 3; +  } + +  // The set of attribute declarations. +  repeated Entry entry = 1; +} + +// A value that represents an array of resource values. +message Array { +  // A single element of the array. +  message Element { +    // Where the element was defined. +    optional Source source = 1; + +    // Any comments associated with the element. +    optional string comment = 2; + +    // The value assigned to this element. +    optional Item item = 3; +  } + +  // The list of array elements. +  repeated Element element = 1; +} + +// A value that represents a string and its many variations based on plurality. +message Plural { +  // The arity of the plural. +  enum Arity { +    ZERO = 0; +    ONE = 1; +    TWO = 2; +    FEW = 3; +    MANY = 4; +    OTHER = 5; +  } + +  // The plural value for a given arity. +  message Entry { +    // Where the plural was defined. +    optional Source source = 1; + +    // Any comments associated with the plural. +    optional string comment = 2; + +    // The arity of the plural. +    optional Arity arity = 3; + +    // The value assigned to this plural. +    optional Item item = 4; +  } + +  // The set of arity/plural mappings. +  repeated Entry entry = 1; +} + +// Defines an abstract XmlNode that must be either an XmlElement, or +// a text node represented by a string. +message XmlNode { +  // If set, this node is an element/tag. +  optional XmlElement element = 1; + +  // If set, this node is a chunk of text. +  optional string text = 2; + +  // Source line and column info. +  optional SourcePosition source = 3; +} + +// An <element> in an XML document. +message XmlElement { +  // Namespaces defined on this element. +  repeated XmlNamespace namespace_declaration = 1; + +  // The namespace URI of this element. +  optional string namespace_uri = 2; + +  // The name of this element. +  optional string name = 3; + +  // The attributes of this element. +  repeated XmlAttribute attribute = 4; + +  // The children of this element. +  repeated XmlNode child = 5; +} + +// A namespace declaration on an XmlElement (xmlns:android="http://..."). +message XmlNamespace { +  optional string prefix = 1; +  optional string uri = 2; + +  // Source line and column info. +  optional SourcePosition source = 3; +} + +// An attribute defined on an XmlElement (android:text="..."). +message XmlAttribute { +  optional string namespace_uri = 1; +  optional string name = 2; +  optional string value = 3; + +  // Source line and column info. +  optional SourcePosition source = 4; + +  // The resource ID (0xPPTTEEEE) of the attribute. +  optional uint32 resource_id = 5; + +  // The interpreted/compiled version of the `value` string. +  optional Item compiled_item = 6; +} diff --git a/tools/aapt2/ResourcesInternal.proto b/tools/aapt2/ResourcesInternal.proto new file mode 100644 index 000000000000..31179174b843 --- /dev/null +++ b/tools/aapt2/ResourcesInternal.proto @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +option java_package = "android.aapt.pb.internal"; +option optimize_for = LITE_RUNTIME; + +import "frameworks/base/tools/aapt2/Resources.proto"; + +package aapt.pb.internal; + +// The top level message representing an external resource file (layout XML, PNG, etc). +// This is used to represent a compiled file before it is linked. Only useful to aapt2. +message CompiledFile { +  message Symbol { +    // The name of the symbol (in the form package:type/name). +    optional string resource_name = 1; + +    // The position in the file at which this symbol is defined. For debug use. +    optional aapt.pb.SourcePosition source = 2; +  } + +  // The name of the resource (in the form package:type/name). +  optional string resource_name = 1; + +  // The configuration for which the resource is defined. +  optional aapt.pb.ConfigDescription config = 2; + +  // The filesystem path to where the source file originated. +  // Mainly used to display helpful error messages. +  optional string source_path = 3; + +  // Any symbols this file auto-generates/exports (eg. @+id/foo in an XML file). +  repeated Symbol exported_symbol = 4; + +  // If this is a compiled XML file, this is the root node. +  optional aapt.pb.XmlNode xml_root = 5; +} diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h index 2763d49f15c4..eb4fa494e53f 100644 --- a/tools/aapt2/ValueVisitor.h +++ b/tools/aapt2/ValueVisitor.h @@ -80,7 +80,7 @@ struct ValueVisitor : public RawValueVisitor {    }    void VisitSubValues(Array* array) { -    for (std::unique_ptr<Item>& item : array->items) { +    for (std::unique_ptr<Item>& item : array->elements) {        item->Accept(this);      }    } diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index b64cd8c432d4..7f5bbf042766 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -16,11 +16,11 @@  #include <dirent.h> -#include <fstream>  #include <string>  #include "android-base/errors.h"  #include "android-base/file.h" +#include "android-base/utf8.h"  #include "androidfw/StringPiece.h"  #include "google/protobuf/io/coded_stream.h"  #include "google/protobuf/io/zero_copy_stream_impl_lite.h" @@ -38,6 +38,7 @@  #include "flatten/Archive.h"  #include "flatten/XmlFlattener.h"  #include "io/BigBufferOutputStream.h" +#include "io/FileInputStream.h"  #include "io/Util.h"  #include "proto/ProtoSerialize.h"  #include "util/Files.h" @@ -46,8 +47,9 @@  #include "xml/XmlDom.h"  #include "xml/XmlPullParser.h" -using android::StringPiece; -using google::protobuf::io::CopyingOutputStreamAdaptor; +using ::aapt::io::FileInputStream; +using ::android::StringPiece; +using ::google::protobuf::io::CopyingOutputStreamAdaptor;  namespace aapt { @@ -57,19 +59,14 @@ struct ResourcePathData {    std::string name;    std::string extension; -  // Original config str. We keep this because when we parse the config, we may -  // add on -  // version qualifiers. We want to preserve the original input so the output is -  // easily +  // Original config str. We keep this because when we parse the config, we may add on +  // version qualifiers. We want to preserve the original input so the output is easily    // computed before hand.    std::string config_str;    ConfigDescription config;  }; -/** - * Resource file paths are expected to look like: - * [--/res/]type[-config]/name - */ +// Resource file paths are expected to look like: [--/res/]type[-config]/name  static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path,                                                         std::string* out_error) {    std::vector<std::string> parts = util::Split(path, file::sDirSep); @@ -137,9 +134,7 @@ static bool IsHidden(const StringPiece& filename) {    return util::StartsWith(filename, ".");  } -/** - * Walks the res directory structure, looking for resource files. - */ +// Walks the res directory structure, looking for resource files.  static bool LoadInputFilesFromDir(IAaptContext* context, const CompileOptions& options,                                    std::vector<ResourcePathData>* out_path_data) {    const std::string& root_dir = options.res_dir.value(); @@ -195,22 +190,20 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options,                           const std::string& output_path) {    ResourceTable table;    { -    std::ifstream fin(path_data.source.path, std::ifstream::binary); -    if (!fin) { +    FileInputStream fin(path_data.source.path); +    if (fin.HadError()) {        context->GetDiagnostics()->Error(DiagMessage(path_data.source) -                                       << "failed to open file: " -                                       << android::base::SystemErrorCodeToString(errno)); +                                       << "failed to open file: " << fin.GetError());        return false;      }      // Parse the values file from XML. -    xml::XmlPullParser xml_parser(fin); +    xml::XmlPullParser xml_parser(&fin);      ResourceParserOptions parser_options;      parser_options.error_on_positional_arguments = !options.legacy_mode; -    // If the filename includes donottranslate, then the default translatable is -    // false. +    // If the filename includes donottranslate, then the default translatable is false.      parser_options.translatable = path_data.name.find("donottranslate") == std::string::npos;      ResourceParser res_parser(context->GetDiagnostics(), &table, path_data.source, path_data.config, @@ -218,8 +211,6 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options,      if (!res_parser.Parse(&xml_parser)) {        return false;      } - -    fin.close();    }    if (options.pseudolocalize) { @@ -239,8 +230,7 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options,    // Assign an ID to any package that has resources.    for (auto& pkg : table.packages) {      if (!pkg->id) { -      // If no package ID was set while parsing (public identifiers), auto -      // assign an ID. +      // If no package ID was set while parsing (public identifiers), auto assign an ID.        pkg->id = context->GetPackageId();      }    } @@ -292,7 +282,7 @@ static bool WriteHeaderAndBufferToWriter(const StringPiece& output_path, const R      // Number of CompiledFiles.      output_stream.WriteLittleEndian32(1); -    std::unique_ptr<pb::CompiledFile> compiled_file = SerializeCompiledFileToPb(file); +    std::unique_ptr<pb::internal::CompiledFile> compiled_file = SerializeCompiledFileToPb(file);      output_stream.WriteCompiledFile(compiled_file.get());      output_stream.WriteData(&buffer); @@ -329,7 +319,7 @@ static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path, const Res      // Number of CompiledFiles.      output_stream.WriteLittleEndian32(1); -    std::unique_ptr<pb::CompiledFile> compiled_file = SerializeCompiledFileToPb(file); +    std::unique_ptr<pb::internal::CompiledFile> compiled_file = SerializeCompiledFileToPb(file);      output_stream.WriteCompiledFile(compiled_file.get());      output_stream.WriteData(map.getDataPtr(), map.getDataLength()); @@ -356,7 +346,8 @@ static bool FlattenXmlToOutStream(IAaptContext* context, const StringPiece& outp      return false;    } -  std::unique_ptr<pb::CompiledFile> pb_compiled_file = SerializeCompiledFileToPb(xmlres->file); +  std::unique_ptr<pb::internal::CompiledFile> pb_compiled_file = +      SerializeCompiledFileToPb(xmlres->file);    out->WriteCompiledFile(pb_compiled_file.get());    out->WriteData(&buffer); @@ -367,7 +358,7 @@ static bool FlattenXmlToOutStream(IAaptContext* context, const StringPiece& outp    return true;  } -static bool IsValidFile(IAaptContext* context, const StringPiece& input_path) { +static bool IsValidFile(IAaptContext* context, const std::string& input_path) {    const file::FileType file_type = file::GetFileType(input_path);    if (file_type != file::FileType::kRegular && file_type != file::FileType::kSymlink) {      if (file_type == file::FileType::kDirectory) { @@ -393,17 +384,14 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options,    std::unique_ptr<xml::XmlResource> xmlres;    { -    std::ifstream fin(path_data.source.path, std::ifstream::binary); -    if (!fin) { +    FileInputStream fin(path_data.source.path); +    if (fin.HadError()) {        context->GetDiagnostics()->Error(DiagMessage(path_data.source) -                                       << "failed to open file: " -                                       << android::base::SystemErrorCodeToString(errno)); +                                       << "failed to open file: " << fin.GetError());        return false;      }      xmlres = xml::Inflate(&fin, context->GetDiagnostics(), path_data.source); - -    fin.close();    }    if (!xmlres) { @@ -432,12 +420,9 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options,      return false;    } -  // Make sure CopyingOutputStreamAdaptor is deleted before we call -  // writer->FinishEntry(). +  // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().    { -    // Wrap our IArchiveWriter with an adaptor that implements the -    // ZeroCopyOutputStream -    // interface. +    // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.      CopyingOutputStreamAdaptor copying_adaptor(writer);      CompiledFileOutputStream output_stream(©ing_adaptor); diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp index aa9472361def..0965910ca853 100644 --- a/tools/aapt2/cmd/Dump.cpp +++ b/tools/aapt2/cmd/Dump.cpp @@ -27,11 +27,11 @@  #include "unflatten/BinaryResourceParser.h"  #include "util/Files.h" -using android::StringPiece; +using ::android::StringPiece;  namespace aapt { -bool DumpCompiledFile(const pb::CompiledFile& pb_file, const void* data, size_t len, +bool DumpCompiledFile(const pb::internal::CompiledFile& pb_file, const void* data, size_t len,                        const Source& source, IAaptContext* context) {    std::unique_ptr<ResourceFile> file =        DeserializeCompiledFileFromPb(pb_file, source, context->GetDiagnostics()); @@ -118,7 +118,7 @@ bool TryDumpFile(IAaptContext* context, const std::string& file_path) {        }        for (uint32_t i = 0; i < num_files; i++) { -        pb::CompiledFile compiled_file; +        pb::internal::CompiledFile compiled_file;          if (!input.ReadCompiledFile(&compiled_file)) {            context->GetDiagnostics()->Warn(DiagMessage() << "failed to read compiled file");            return false; diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 5ad0cdd995ed..d9e7ac6c1e08 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -40,6 +40,7 @@  #include "flatten/TableFlattener.h"  #include "flatten/XmlFlattener.h"  #include "io/BigBufferInputStream.h" +#include "io/FileInputStream.h"  #include "io/FileSystem.h"  #include "io/Util.h"  #include "io/ZipArchive.h" @@ -61,8 +62,9 @@  #include "util/Files.h"  #include "xml/XmlDom.h" -using android::StringPiece; -using android::base::StringPrintf; +using ::aapt::io::FileInputStream; +using ::android::StringPiece; +using ::android::base::StringPrintf;  namespace aapt { @@ -284,13 +286,11 @@ static std::unique_ptr<ResourceTable> LoadTableFromPb(const Source& source, cons    return table;  } -/** - * Inflates an XML file from the source path. - */ +// Inflates an XML file from the source path.  static std::unique_ptr<xml::XmlResource> LoadXml(const std::string& path, IDiagnostics* diag) { -  std::ifstream fin(path, std::ifstream::binary); -  if (!fin) { -    diag->Error(DiagMessage(path) << strerror(errno)); +  FileInputStream fin(path); +  if (fin.HadError()) { +    diag->Error(DiagMessage(path) << "failed to load XML file: " << fin.GetError());      return {};    }    return xml::Inflate(&fin, diag, Source(path)); @@ -482,7 +482,7 @@ std::vector<std::unique_ptr<xml::XmlResource>> ResourceFileFlattener::LinkAndVer    if (options_.no_version_vectors || options_.no_version_transitions) {      // Skip this if it is a vector or animated-vector. -    xml::Element* el = xml::FindRootElement(doc); +    xml::Element* el = doc->root.get();      if (el && el->namespace_uri.empty()) {        if ((options_.no_version_vectors && IsVectorElement(el->name)) ||            (options_.no_version_transitions && IsTransitionElement(el->name))) { @@ -1295,7 +1295,7 @@ class LinkCommand {        }        for (uint32_t i = 0; i < num_files; i++) { -        pb::CompiledFile compiled_file; +        pb::internal::CompiledFile compiled_file;          if (!input_stream.ReadCompiledFile(&compiled_file)) {            context_->GetDiagnostics()->Error(DiagMessage(src)                                              << "failed to read compiled file header"); diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp index e1c45d68f611..d17858d45d08 100644 --- a/tools/aapt2/cmd/Util.cpp +++ b/tools/aapt2/cmd/Util.cpp @@ -28,7 +28,7 @@  #include "util/Maybe.h"  #include "util/Util.h" -using android::StringPiece; +using ::android::StringPiece;  namespace aapt { @@ -134,19 +134,21 @@ static xml::AaptAttribute CreateAttributeWithId(const ResourceId& id) {    return xml::AaptAttribute(Attribute(), id);  } +static xml::NamespaceDecl CreateAndroidNamespaceDecl() { +  xml::NamespaceDecl decl; +  decl.prefix = "android"; +  decl.uri = xml::kSchemaAndroid; +  return decl; +} +  std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info,                                                          const SplitConstraints& constraints) {    const ResourceId kVersionCode(0x0101021b);    const ResourceId kRevisionCode(0x010104d5);    const ResourceId kHasCode(0x0101000c); -  std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>(); - -  std::unique_ptr<xml::Namespace> namespace_android = util::make_unique<xml::Namespace>(); -  namespace_android->namespace_uri = xml::kSchemaAndroid; -  namespace_android->namespace_prefix = "android"; -    std::unique_ptr<xml::Element> manifest_el = util::make_unique<xml::Element>(); +  manifest_el->namespace_decls.push_back(CreateAndroidNamespaceDecl());    manifest_el->name = "manifest";    manifest_el->attributes.push_back(xml::Attribute{"", "package", app_info.package}); @@ -179,8 +181,8 @@ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info,          xml::Attribute{"", "configForSplit", app_info.split_name.value()});    } -  // Splits may contain more configurations than originally desired (fallback densities, etc.). -  // This makes programmatic discovery of split targetting difficult. Encode the original +  // Splits may contain more configurations than originally desired (fall-back densities, etc.). +  // This makes programmatic discovery of split targeting difficult. Encode the original    // split constraints intended for this split.    std::stringstream target_config_str;    target_config_str << util::Joiner(constraints.configs, ","); @@ -193,8 +195,9 @@ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info,                       util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_BOOLEAN, 0u)});    manifest_el->AppendChild(std::move(application_el)); -  namespace_android->AppendChild(std::move(manifest_el)); -  doc->root = std::move(namespace_android); + +  std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>(); +  doc->root = std::move(manifest_el);    return doc;  } @@ -284,7 +287,7 @@ static Maybe<int> ExtractSdkVersion(xml::Attribute* attr, std::string* out_error  Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(xml::XmlResource* xml_res, IDiagnostics* diag) {    // Make sure the first element is <manifest> with package attribute. -  xml::Element* manifest_el = xml::FindRootElement(xml_res->root.get()); +  xml::Element* manifest_el = xml_res->root.get();    if (manifest_el == nullptr) {      return {};    } diff --git a/tools/aapt2/compile/InlineXmlFormatParser.cpp b/tools/aapt2/compile/InlineXmlFormatParser.cpp index 786494b6ad1c..857cdd5365a0 100644 --- a/tools/aapt2/compile/InlineXmlFormatParser.cpp +++ b/tools/aapt2/compile/InlineXmlFormatParser.cpp @@ -16,12 +16,8 @@  #include "compile/InlineXmlFormatParser.h" -#include <sstream>  #include <string> -#include "android-base/macros.h" - -#include "Debug.h"  #include "ResourceUtils.h"  #include "util/Util.h"  #include "xml/XmlDom.h" @@ -31,19 +27,17 @@ namespace aapt {  namespace { -/** - * XML Visitor that will find all <aapt:attr> elements for extraction. - */ +struct InlineDeclaration { +  xml::Element* el; +  std::string attr_namespace_uri; +  std::string attr_name; +}; + +// XML Visitor that will find all <aapt:attr> elements for extraction.  class Visitor : public xml::PackageAwareVisitor {   public:    using xml::PackageAwareVisitor::Visit; -  struct InlineDeclaration { -    xml::Element* el; -    std::string attr_namespace_uri; -    std::string attr_name; -  }; -    explicit Visitor(IAaptContext* context, xml::XmlResource* xml_resource)        : context_(context), xml_resource_(xml_resource) {} @@ -53,51 +47,44 @@ class Visitor : public xml::PackageAwareVisitor {        return;      } -    const Source& src = xml_resource_->file.source.WithLine(el->line_number); +    const Source src = xml_resource_->file.source.WithLine(el->line_number);      xml::Attribute* attr = el->FindAttribute({}, "name");      if (!attr) { -      context_->GetDiagnostics()->Error(DiagMessage(src) -                                        << "missing 'name' attribute"); +      context_->GetDiagnostics()->Error(DiagMessage(src) << "missing 'name' attribute");        error_ = true;        return;      }      Maybe<Reference> ref = ResourceUtils::ParseXmlAttributeName(attr->value);      if (!ref) { -      context_->GetDiagnostics()->Error( -          DiagMessage(src) << "invalid XML attribute '" << attr->value << "'"); +      context_->GetDiagnostics()->Error(DiagMessage(src) << "invalid XML attribute '" << attr->value +                                                         << "'");        error_ = true;        return;      }      const ResourceName& name = ref.value().name.value(); -    // Use an empty string for the compilation package because we don't want to -    // default to -    // the local package if the user specified name="style" or something. This -    // should just +    // Use an empty string for the compilation package because we don't want to default to +    // the local package if the user specified name="style" or something. This should just      // be the default namespace. -    Maybe<xml::ExtractedPackage> maybe_pkg = -        TransformPackageAlias(name.package, {}); +    Maybe<xml::ExtractedPackage> maybe_pkg = TransformPackageAlias(name.package, {});      if (!maybe_pkg) { -      context_->GetDiagnostics()->Error(DiagMessage(src) -                                        << "invalid namespace prefix '" -                                        << name.package << "'"); +      context_->GetDiagnostics()->Error(DiagMessage(src) << "invalid namespace prefix '" +                                                         << name.package << "'");        error_ = true;        return;      }      const xml::ExtractedPackage& pkg = maybe_pkg.value(); -    const bool private_namespace = -        pkg.private_namespace || ref.value().private_reference; +    const bool private_namespace = pkg.private_namespace || ref.value().private_reference;      InlineDeclaration decl;      decl.el = el;      decl.attr_name = name.entry;      if (!pkg.package.empty()) { -      decl.attr_namespace_uri = -          xml::BuildPackageNamespace(pkg.package, private_namespace); +      decl.attr_namespace_uri = xml::BuildPackageNamespace(pkg.package, private_namespace);      }      inline_declarations_.push_back(std::move(decl)); @@ -107,7 +94,9 @@ class Visitor : public xml::PackageAwareVisitor {      return inline_declarations_;    } -  bool HasError() const { return error_; } +  bool HasError() const { +    return error_; +  }   private:    DISALLOW_COPY_AND_ASSIGN(Visitor); @@ -120,8 +109,7 @@ class Visitor : public xml::PackageAwareVisitor {  }  // namespace -bool InlineXmlFormatParser::Consume(IAaptContext* context, -                                    xml::XmlResource* doc) { +bool InlineXmlFormatParser::Consume(IAaptContext* context, xml::XmlResource* doc) {    Visitor visitor(context, doc);    doc->root->Accept(&visitor);    if (visitor.HasError()) { @@ -129,69 +117,53 @@ bool InlineXmlFormatParser::Consume(IAaptContext* context,    }    size_t name_suffix_counter = 0; -  for (const Visitor::InlineDeclaration& decl : -       visitor.GetInlineDeclarations()) { +  for (const InlineDeclaration& decl : visitor.GetInlineDeclarations()) {      auto new_doc = util::make_unique<xml::XmlResource>();      new_doc->file.config = doc->file.config;      new_doc->file.source = doc->file.source.WithLine(decl.el->line_number);      new_doc->file.name = doc->file.name;      // Modify the new entry name. We need to suffix the entry with a number to -    // avoid -    // local collisions, then mangle it with the empty package, such that it -    // won't show up +    // avoid local collisions, then mangle it with the empty package, such that it won't show up      // in R.java. - -    new_doc->file.name.entry = -        NameMangler::MangleEntry({}, new_doc->file.name.entry + "__" + -                                         std::to_string(name_suffix_counter)); +    new_doc->file.name.entry = NameMangler::MangleEntry( +        {}, new_doc->file.name.entry + "__" + std::to_string(name_suffix_counter));      // Extracted elements must be the only child of <aapt:attr>.      // Make sure there is one root node in the children (ignore empty text). -    for (auto& child : decl.el->children) { +    for (std::unique_ptr<xml::Node>& child : decl.el->children) {        const Source child_source = doc->file.source.WithLine(child->line_number);        if (xml::Text* t = xml::NodeCast<xml::Text>(child.get())) {          if (!util::TrimWhitespace(t->text).empty()) { -          context->GetDiagnostics()->Error( -              DiagMessage(child_source) -              << "can't extract text into its own resource"); +          context->GetDiagnostics()->Error(DiagMessage(child_source) +                                           << "can't extract text into its own resource");            return false;          }        } else if (new_doc->root) { -        context->GetDiagnostics()->Error( -            DiagMessage(child_source) -            << "inline XML resources must have a single root"); +        context->GetDiagnostics()->Error(DiagMessage(child_source) +                                         << "inline XML resources must have a single root");          return false;        } else { -        new_doc->root = std::move(child); +        new_doc->root.reset(static_cast<xml::Element*>(child.release()));          new_doc->root->parent = nullptr;        }      } -    // Walk up and find the parent element. -    xml::Node* node = decl.el; -    xml::Element* parent_el = nullptr; -    while (node->parent && -           (parent_el = xml::NodeCast<xml::Element>(node->parent)) == nullptr) { -      node = node->parent; -    } - +    // Get the parent element of <aapt:attr> +    xml::Element* parent_el = decl.el->parent;      if (!parent_el) { -      context->GetDiagnostics()->Error( -          DiagMessage(new_doc->file.source) -          << "no suitable parent for inheriting attribute"); +      context->GetDiagnostics()->Error(DiagMessage(new_doc->file.source) +                                       << "no suitable parent for inheriting attribute");        return false;      }      // Add the inline attribute to the parent. -    parent_el->attributes.push_back( -        xml::Attribute{decl.attr_namespace_uri, decl.attr_name, -                       "@" + new_doc->file.name.ToString()}); +    parent_el->attributes.push_back(xml::Attribute{decl.attr_namespace_uri, decl.attr_name, +                                                   "@" + new_doc->file.name.ToString()});      // Delete the subtree. -    for (auto iter = parent_el->children.begin(); -         iter != parent_el->children.end(); ++iter) { -      if (iter->get() == node) { +    for (auto iter = parent_el->children.begin(); iter != parent_el->children.end(); ++iter) { +      if (iter->get() == decl.el) {          parent_el->children.erase(iter);          break;        } diff --git a/tools/aapt2/compile/InlineXmlFormatParser.h b/tools/aapt2/compile/InlineXmlFormatParser.h index 1a658fd6a180..4300023e7726 100644 --- a/tools/aapt2/compile/InlineXmlFormatParser.h +++ b/tools/aapt2/compile/InlineXmlFormatParser.h @@ -26,35 +26,30 @@  namespace aapt { -/** - * Extracts Inline XML definitions into their own xml::XmlResource objects. - * - * Inline XML looks like: - * - * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" - *                  xmlns:aapt="http://schemas.android.com/aapt" > - *   <aapt:attr name="android:drawable" > - *     <vector - *       android:height="64dp" - *       android:width="64dp" - *       android:viewportHeight="600" - *       android:viewportWidth="600"/> - *   </aapt:attr> - * </animated-vector> - * - * The <vector> will be extracted into its own XML file and <animated-vector> - * will - * gain an attribute 'android:drawable' set to a reference to the extracted - * <vector> resource. - */ +// Extracts Inline XML definitions into their own xml::XmlResource objects. +// +// Inline XML looks like: +// +// <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" +//                  xmlns:aapt="http://schemas.android.com/aapt" > +//   <aapt:attr name="android:drawable" > +//     <vector +//       android:height="64dp" +//       android:width="64dp" +//       android:viewportHeight="600" +//       android:viewportWidth="600"/> +//   </aapt:attr> +// </animated-vector> +// +// The <vector> will be extracted into its own XML file and <animated-vector> will +// gain an attribute 'android:drawable' set to a reference to the extracted <vector> resource.  class InlineXmlFormatParser : public IXmlResourceConsumer {   public:    explicit InlineXmlFormatParser() = default;    bool Consume(IAaptContext* context, xml::XmlResource* doc) override; -  std::vector<std::unique_ptr<xml::XmlResource>>& -  GetExtractedInlineXmlDocuments() { +  std::vector<std::unique_ptr<xml::XmlResource>>& GetExtractedInlineXmlDocuments() {      return queue_;    } diff --git a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp index 348796c98c22..de7739ada407 100644 --- a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp +++ b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp @@ -18,25 +18,32 @@  #include "test/Test.h" +using ::testing::Eq; +using ::testing::IsNull; +using ::testing::Not; +using ::testing::NotNull; +using ::testing::SizeIs; +using ::testing::StrEq; +  namespace aapt {  TEST(InlineXmlFormatParserTest, PassThrough) {    std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); -  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( +  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(        <View xmlns:android="http://schemas.android.com/apk/res/android">          <View android:text="hey">            <View android:id="hi" />          </View> -      </View>)EOF"); +      </View>)");    InlineXmlFormatParser parser;    ASSERT_TRUE(parser.Consume(context.get(), doc.get())); -  EXPECT_EQ(0u, parser.GetExtractedInlineXmlDocuments().size()); +  EXPECT_THAT(parser.GetExtractedInlineXmlDocuments(), SizeIs(0u));  }  TEST(InlineXmlFormatParserTest, ExtractOneXmlResource) {    std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); -  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( +  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(        <View1 xmlns:android="http://schemas.android.com/apk/res/android"              xmlns:aapt="http://schemas.android.com/aapt">          <aapt:attr name="android:text"> @@ -44,7 +51,7 @@ TEST(InlineXmlFormatParserTest, ExtractOneXmlResource) {              <View3 android:id="hi" />            </View2>          </aapt:attr> -      </View1>)EOF"); +      </View1>)");    doc->file.name = test::ParseNameOrDie("layout/main"); @@ -52,42 +59,38 @@ TEST(InlineXmlFormatParserTest, ExtractOneXmlResource) {    ASSERT_TRUE(parser.Consume(context.get(), doc.get()));    // One XML resource should have been extracted. -  EXPECT_EQ(1u, parser.GetExtractedInlineXmlDocuments().size()); - -  xml::Element* el = xml::FindRootElement(doc.get()); -  ASSERT_NE(nullptr, el); +  EXPECT_THAT(parser.GetExtractedInlineXmlDocuments(), SizeIs(1u)); -  EXPECT_EQ("View1", el->name); +  xml::Element* el = doc->root.get(); +  ASSERT_THAT(el, NotNull()); +  EXPECT_THAT(el->name, StrEq("View1"));    // The <aapt:attr> tag should be extracted. -  EXPECT_EQ(nullptr, el->FindChild(xml::kSchemaAapt, "attr")); +  EXPECT_THAT(el->FindChild(xml::kSchemaAapt, "attr"), IsNull());    // The 'android:text' attribute should be set with a reference.    xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "text"); -  ASSERT_NE(nullptr, attr); +  ASSERT_THAT(attr, NotNull());    ResourceNameRef name_ref;    ASSERT_TRUE(ResourceUtils::ParseReference(attr->value, &name_ref)); -  xml::XmlResource* extracted_doc = -      parser.GetExtractedInlineXmlDocuments()[0].get(); -  ASSERT_NE(nullptr, extracted_doc); +  xml::XmlResource* extracted_doc = parser.GetExtractedInlineXmlDocuments()[0].get(); +  ASSERT_THAT(extracted_doc, NotNull());    // Make sure the generated reference is correct. -  EXPECT_EQ(name_ref.package, extracted_doc->file.name.package); -  EXPECT_EQ(name_ref.type, extracted_doc->file.name.type); -  EXPECT_EQ(name_ref.entry, extracted_doc->file.name.entry); +  EXPECT_THAT(extracted_doc->file.name, Eq(name_ref));    // Verify the structure of the extracted XML. -  el = xml::FindRootElement(extracted_doc); -  ASSERT_NE(nullptr, el); -  EXPECT_EQ("View2", el->name); -  EXPECT_NE(nullptr, el->FindChild({}, "View3")); +  el = extracted_doc->root.get(); +  ASSERT_THAT(el, NotNull()); +  EXPECT_THAT(el->name, StrEq("View2")); +  EXPECT_THAT(el->FindChild({}, "View3"), NotNull());  }  TEST(InlineXmlFormatParserTest, ExtractTwoXmlResources) {    std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); -  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( +  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(        <View1 xmlns:android="http://schemas.android.com/apk/res/android"              xmlns:aapt="http://schemas.android.com/aapt">          <aapt:attr name="android:text"> @@ -99,45 +102,39 @@ TEST(InlineXmlFormatParserTest, ExtractTwoXmlResources) {          <aapt:attr name="android:drawable">            <vector />          </aapt:attr> -      </View1>)EOF"); +      </View1>)");    doc->file.name = test::ParseNameOrDie("layout/main");    InlineXmlFormatParser parser;    ASSERT_TRUE(parser.Consume(context.get(), doc.get())); -  ASSERT_EQ(2u, parser.GetExtractedInlineXmlDocuments().size()); - -  xml::Element* el = xml::FindRootElement(doc.get()); -  ASSERT_NE(nullptr, el); +  ASSERT_THAT(parser.GetExtractedInlineXmlDocuments(), SizeIs(2u)); -  EXPECT_EQ("View1", el->name); +  xml::Element* el = doc->root.get(); +  ASSERT_THAT(el, NotNull()); +  EXPECT_THAT(el->name, StrEq("View1"));    xml::Attribute* attr_text = el->FindAttribute(xml::kSchemaAndroid, "text"); -  ASSERT_NE(nullptr, attr_text); +  ASSERT_THAT(attr_text, NotNull()); -  xml::Attribute* attr_drawable = -      el->FindAttribute(xml::kSchemaAndroid, "drawable"); -  ASSERT_NE(nullptr, attr_drawable); +  xml::Attribute* attr_drawable = el->FindAttribute(xml::kSchemaAndroid, "drawable"); +  ASSERT_THAT(attr_drawable, NotNull());    // The two extracted resources should have different names. -  EXPECT_NE(attr_text->value, attr_drawable->value); +  EXPECT_THAT(attr_text->value, Not(Eq(attr_drawable->value)));    // The child <aapt:attr> elements should be gone. -  EXPECT_EQ(nullptr, el->FindChild(xml::kSchemaAapt, "attr")); - -  xml::XmlResource* extracted_doc_text = -      parser.GetExtractedInlineXmlDocuments()[0].get(); -  ASSERT_NE(nullptr, extracted_doc_text); -  el = xml::FindRootElement(extracted_doc_text); -  ASSERT_NE(nullptr, el); -  EXPECT_EQ("View2", el->name); - -  xml::XmlResource* extracted_doc_drawable = -      parser.GetExtractedInlineXmlDocuments()[1].get(); -  ASSERT_NE(nullptr, extracted_doc_drawable); -  el = xml::FindRootElement(extracted_doc_drawable); -  ASSERT_NE(nullptr, el); -  EXPECT_EQ("vector", el->name); +  EXPECT_THAT(el->FindChild(xml::kSchemaAapt, "attr"), IsNull()); + +  xml::XmlResource* extracted_doc_text = parser.GetExtractedInlineXmlDocuments()[0].get(); +  ASSERT_THAT(extracted_doc_text, NotNull()); +  ASSERT_THAT(extracted_doc_text->root, NotNull()); +  EXPECT_THAT(extracted_doc_text->root->name, StrEq("View2")); + +  xml::XmlResource* extracted_doc_drawable = parser.GetExtractedInlineXmlDocuments()[1].get(); +  ASSERT_THAT(extracted_doc_drawable, NotNull()); +  ASSERT_THAT(extracted_doc_drawable->root, NotNull()); +  EXPECT_THAT(extracted_doc_drawable->root->name, StrEq("vector"));  }  }  // namespace aapt diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp index d051120b9445..bdccf8bcae3a 100644 --- a/tools/aapt2/configuration/ConfigurationParser.cpp +++ b/tools/aapt2/configuration/ConfigurationParser.cpp @@ -22,13 +22,14 @@  #include <memory>  #include <utility> -#include <android-base/file.h> -#include <android-base/logging.h> +#include "android-base/file.h" +#include "android-base/logging.h"  #include "ConfigDescription.h"  #include "Diagnostics.h"  #include "io/File.h"  #include "io/FileSystem.h" +#include "io/StringInputStream.h"  #include "util/Maybe.h"  #include "util/Util.h"  #include "xml/XmlActionExecutor.h" @@ -49,9 +50,9 @@ using ::aapt::configuration::Group;  using ::aapt::configuration::Locale;  using ::aapt::io::IFile;  using ::aapt::io::RegularFile; +using ::aapt::io::StringInputStream;  using ::aapt::util::TrimWhitespace;  using ::aapt::xml::Element; -using ::aapt::xml::FindRootElement;  using ::aapt::xml::NodeCast;  using ::aapt::xml::XmlActionExecutor;  using ::aapt::xml::XmlActionExecutorPolicy; @@ -182,15 +183,14 @@ ConfigurationParser::ConfigurationParser(std::string contents)  }  Maybe<PostProcessingConfiguration> ConfigurationParser::Parse() { -  std::istringstream in(contents_); - -  auto doc = xml::Inflate(&in, diag_, Source("config.xml")); +  StringInputStream in(contents_); +  std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag_, Source("config.xml"));    if (!doc) {      return {};    }    // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace. -  auto* root = FindRootElement(doc.get()); +  Element* root = doc->root.get();    if (root == nullptr) {      diag_->Error(DiagMessage() << "Could not find the root element in the XML document");      return {}; diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp index fb71e98d2fb5..f89773720cc5 100644 --- a/tools/aapt2/configuration/ConfigurationParser_test.cpp +++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp @@ -18,9 +18,6 @@  #include <string> -#include <gmock/gmock.h> -#include <gtest/gtest.h> -  #include "androidfw/ResourceTypes.h"  #include "test/Test.h" @@ -29,7 +26,7 @@  namespace aapt {  namespace { -using android::ResTable_config; +using ::android::ResTable_config;  using configuration::Abi;  using configuration::AndroidSdk;  using configuration::Artifact; @@ -38,7 +35,7 @@ using configuration::DeviceFeature;  using configuration::GlTexture;  using configuration::Locale;  using configuration::AndroidManifest; -using testing::ElementsAre; +using ::testing::ElementsAre;  using xml::Element;  using xml::NodeCast; @@ -192,7 +189,7 @@ TEST_F(ConfigurationParserTest, ArtifactAction) {    auto doc = test::BuildXmlDom(xml);    PostProcessingConfiguration config; -  bool ok = artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); +  bool ok = artifact_handler_(&config, NodeCast<Element>(doc->root.get()), &diag_);    ASSERT_TRUE(ok);    EXPECT_EQ(1ul, config.artifacts.size()); diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp index 826f91b4a2fd..5f8bd063f9b0 100644 --- a/tools/aapt2/flatten/Archive.cpp +++ b/tools/aapt2/flatten/Archive.cpp @@ -23,12 +23,14 @@  #include "android-base/errors.h"  #include "android-base/macros.h" +#include "android-base/utf8.h"  #include "androidfw/StringPiece.h"  #include "ziparchive/zip_writer.h"  #include "util/Files.h" -using android::StringPiece; +using ::android::StringPiece; +using ::android::base::SystemErrorCodeToString;  namespace aapt { @@ -58,11 +60,11 @@ class DirectoryWriter : public IArchiveWriter {      std::string full_path = dir_;      file::AppendPath(&full_path, path); -    file::mkdirs(file::GetStem(full_path)); +    file::mkdirs(file::GetStem(full_path).to_string()); -    file_ = {fopen(full_path.data(), "wb"), fclose}; +    file_ = {::android::base::utf8::fopen(full_path.c_str(), "wb"), fclose};      if (!file_) { -      error_ = android::base::SystemErrorCodeToString(errno); +      error_ = SystemErrorCodeToString(errno);        return false;      }      return true; @@ -74,7 +76,7 @@ class DirectoryWriter : public IArchiveWriter {      }      if (fwrite(data, 1, len, file_.get()) != static_cast<size_t>(len)) { -      error_ = android::base::SystemErrorCodeToString(errno); +      error_ = SystemErrorCodeToString(errno);        file_.reset(nullptr);        return false;      } @@ -121,9 +123,9 @@ class ZipFileWriter : public IArchiveWriter {    ZipFileWriter() = default;    bool Open(const StringPiece& path) { -    file_ = {fopen(path.data(), "w+b"), fclose}; +    file_ = {::android::base::utf8::fopen(path.to_string().c_str(), "w+b"), fclose};      if (!file_) { -      error_ = android::base::SystemErrorCodeToString(errno); +      error_ = SystemErrorCodeToString(errno);        return false;      }      writer_ = util::make_unique<ZipWriter>(file_.get()); diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp index e5993a65366d..14b776b1bd99 100644 --- a/tools/aapt2/flatten/TableFlattener.cpp +++ b/tools/aapt2/flatten/TableFlattener.cpp @@ -133,7 +133,7 @@ class MapFlattenVisitor : public RawValueVisitor {    }    void Visit(Array* array) override { -    for (auto& item : array->items) { +    for (auto& item : array->elements) {        ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>();        FlattenValue(item.get(), out_entry);        out_entry->value.size = util::HostToDevice16(sizeof(out_entry->value)); diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp index 331ef784a7da..b3b308a29fc5 100644 --- a/tools/aapt2/flatten/XmlFlattener.cpp +++ b/tools/aapt2/flatten/XmlFlattener.cpp @@ -38,12 +38,10 @@ namespace {  constexpr uint32_t kLowPriority = 0xffffffffu; -static bool cmp_xml_attribute_by_id(const xml::Attribute* a, -                                    const xml::Attribute* b) { +static bool cmp_xml_attribute_by_id(const xml::Attribute* a, const xml::Attribute* b) {    if (a->compiled_attribute && a->compiled_attribute.value().id) {      if (b->compiled_attribute && b->compiled_attribute.value().id) { -      return a->compiled_attribute.value().id.value() < -             b->compiled_attribute.value().id.value(); +      return a->compiled_attribute.value().id.value() < b->compiled_attribute.value().id.value();      }      return true;    } else if (!b->compiled_attribute) { @@ -75,17 +73,6 @@ class XmlFlattenerVisitor : public xml::Visitor {    XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options)        : buffer_(buffer), options_(options) {} -  void Visit(xml::Namespace* node) override { -    if (node->namespace_uri == xml::kSchemaTools) { -      // Skip dedicated tools namespace. -      xml::Visitor::Visit(node); -    } else { -      WriteNamespace(node, android::RES_XML_START_NAMESPACE_TYPE); -      xml::Visitor::Visit(node); -      WriteNamespace(node, android::RES_XML_END_NAMESPACE_TYPE); -    } -  } -    void Visit(xml::Text* node) override {      if (util::TrimWhitespace(node->text).empty()) {        // Skip whitespace only text nodes. @@ -93,8 +80,7 @@ class XmlFlattenerVisitor : public xml::Visitor {      }      ChunkWriter writer(buffer_); -    ResXMLTree_node* flat_node = -        writer.StartChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE); +    ResXMLTree_node* flat_node = writer.StartChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE);      flat_node->lineNumber = util::HostToDevice32(node->line_number);      flat_node->comment.index = util::HostToDevice32(-1); @@ -109,6 +95,13 @@ class XmlFlattenerVisitor : public xml::Visitor {    }    void Visit(xml::Element* node) override { +    for (const xml::NamespaceDecl& decl : node->namespace_decls) { +      // Skip dedicated tools namespace. +      if (decl.uri != xml::kSchemaTools) { +        WriteNamespace(decl, android::RES_XML_START_NAMESPACE_TYPE); +      } +    } +      {        ChunkWriter start_writer(buffer_);        ResXMLTree_node* flat_node = @@ -116,19 +109,15 @@ class XmlFlattenerVisitor : public xml::Visitor {        flat_node->lineNumber = util::HostToDevice32(node->line_number);        flat_node->comment.index = util::HostToDevice32(-1); -      ResXMLTree_attrExt* flat_elem = -          start_writer.NextBlock<ResXMLTree_attrExt>(); +      ResXMLTree_attrExt* flat_elem = start_writer.NextBlock<ResXMLTree_attrExt>(); -      // A missing namespace must be null, not an empty string. Otherwise the -      // runtime complains. +      // A missing namespace must be null, not an empty string. Otherwise the runtime complains.        AddString(node->namespace_uri, kLowPriority, &flat_elem->ns,                  true /* treat_empty_string_as_null */); -      AddString(node->name, kLowPriority, &flat_elem->name, -                true /* treat_empty_string_as_null */); +      AddString(node->name, kLowPriority, &flat_elem->name, true /* treat_empty_string_as_null */);        flat_elem->attributeStart = util::HostToDevice16(sizeof(*flat_elem)); -      flat_elem->attributeSize = -          util::HostToDevice16(sizeof(ResXMLTree_attribute)); +      flat_elem->attributeSize = util::HostToDevice16(sizeof(ResXMLTree_attribute));        WriteAttributes(node, flat_elem, &start_writer); @@ -144,14 +133,20 @@ class XmlFlattenerVisitor : public xml::Visitor {        flat_end_node->lineNumber = util::HostToDevice32(node->line_number);        flat_end_node->comment.index = util::HostToDevice32(-1); -      ResXMLTree_endElementExt* flat_end_elem = -          end_writer.NextBlock<ResXMLTree_endElementExt>(); +      ResXMLTree_endElementExt* flat_end_elem = end_writer.NextBlock<ResXMLTree_endElementExt>();        AddString(node->namespace_uri, kLowPriority, &flat_end_elem->ns,                  true /* treat_empty_string_as_null */);        AddString(node->name, kLowPriority, &flat_end_elem->name);        end_writer.Finish();      } + +    for (auto iter = node->namespace_decls.rbegin(); iter != node->namespace_decls.rend(); ++iter) { +      // Skip dedicated tools namespace. +      if (iter->uri != xml::kSchemaTools) { +        WriteNamespace(*iter, android::RES_XML_END_NAMESPACE_TYPE); +      } +    }    }   private: @@ -173,16 +168,16 @@ class XmlFlattenerVisitor : public xml::Visitor {      string_refs.push_back(StringFlattenDest{ref, dest});    } -  void WriteNamespace(xml::Namespace* node, uint16_t type) { +  void WriteNamespace(const xml::NamespaceDecl& decl, uint16_t type) {      ChunkWriter writer(buffer_);      ResXMLTree_node* flatNode = writer.StartChunk<ResXMLTree_node>(type); -    flatNode->lineNumber = util::HostToDevice32(node->line_number); +    flatNode->lineNumber = util::HostToDevice32(decl.line_number);      flatNode->comment.index = util::HostToDevice32(-1);      ResXMLTree_namespaceExt* flat_ns = writer.NextBlock<ResXMLTree_namespaceExt>(); -    AddString(node->namespace_prefix, kLowPriority, &flat_ns->prefix); -    AddString(node->namespace_uri, kLowPriority, &flat_ns->uri); +    AddString(decl.prefix, kLowPriority, &flat_ns->prefix); +    AddString(decl.uri, kLowPriority, &flat_ns->uri);      writer.Finish();    } diff --git a/tools/aapt2/integration-tests/AppOne/res/navigation/home.xml b/tools/aapt2/integration-tests/AppOne/res/navigation/home.xml new file mode 100644 index 000000000000..ade271d60ab6 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/navigation/home.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<navigation /> diff --git a/tools/aapt2/io/FileInputStream.cpp b/tools/aapt2/io/FileInputStream.cpp new file mode 100644 index 000000000000..07dbb5a98add --- /dev/null +++ b/tools/aapt2/io/FileInputStream.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "io/FileInputStream.h" + +#include <errno.h>   // for errno +#include <fcntl.h>   // for O_RDONLY +#include <unistd.h>  // for read + +#include "android-base/errors.h" +#include "android-base/file.h"  // for O_BINARY +#include "android-base/macros.h" +#include "android-base/utf8.h" + +using ::android::base::SystemErrorCodeToString; + +namespace aapt { +namespace io { + +FileInputStream::FileInputStream(const std::string& path, size_t buffer_capacity) +    : FileInputStream(::android::base::utf8::open(path.c_str(), O_RDONLY | O_BINARY), +                      buffer_capacity) { +} + +FileInputStream::FileInputStream(int fd, size_t buffer_capacity) +    : fd_(fd), +      buffer_capacity_(buffer_capacity), +      buffer_offset_(0u), +      buffer_size_(0u), +      total_byte_count_(0u) { +  if (fd_ == -1) { +    error_ = SystemErrorCodeToString(errno); +  } else { +    buffer_.reset(new uint8_t[buffer_capacity_]); +  } +} + +bool FileInputStream::Next(const void** data, size_t* size) { +  if (HadError()) { +    return false; +  } + +  // Deal with any remaining bytes after BackUp was called. +  if (buffer_offset_ != buffer_size_) { +    *data = buffer_.get() + buffer_offset_; +    *size = buffer_size_ - buffer_offset_; +    total_byte_count_ += buffer_size_ - buffer_offset_; +    buffer_offset_ = buffer_size_; +    return true; +  } + +  ssize_t n = TEMP_FAILURE_RETRY(read(fd_, buffer_.get(), buffer_capacity_)); +  if (n < 0) { +    error_ = SystemErrorCodeToString(errno); +    fd_.reset(); +    return false; +  } + +  buffer_size_ = static_cast<size_t>(n); +  buffer_offset_ = buffer_size_; +  total_byte_count_ += buffer_size_; + +  *data = buffer_.get(); +  *size = buffer_size_; +  return buffer_size_ != 0u; +} + +void FileInputStream::BackUp(size_t count) { +  if (count > buffer_offset_) { +    count = buffer_offset_; +  } +  buffer_offset_ -= count; +  total_byte_count_ -= count; +} + +size_t FileInputStream::ByteCount() const { +  return total_byte_count_; +} + +bool FileInputStream::HadError() const { +  return !error_.empty(); +} + +std::string FileInputStream::GetError() const { +  return error_; +} + +}  // namespace io +}  // namespace aapt diff --git a/tools/aapt2/io/FileInputStream.h b/tools/aapt2/io/FileInputStream.h new file mode 100644 index 000000000000..6beb9a186ce5 --- /dev/null +++ b/tools/aapt2/io/FileInputStream.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_IO_FILEINPUTSTREAM_H +#define AAPT_IO_FILEINPUTSTREAM_H + +#include "io/Io.h" + +#include <memory> +#include <string> + +#include "android-base/macros.h" +#include "android-base/unique_fd.h" + +namespace aapt { +namespace io { + +class FileInputStream : public InputStream { + public: +  explicit FileInputStream(const std::string& path, size_t buffer_capacity = 4096); + +  // Takes ownership of `fd`. +  explicit FileInputStream(int fd, size_t buffer_capacity = 4096); + +  bool Next(const void** data, size_t* size) override; + +  void BackUp(size_t count) override; + +  size_t ByteCount() const override; + +  bool HadError() const override; + +  std::string GetError() const override; + + private: +  DISALLOW_COPY_AND_ASSIGN(FileInputStream); + +  android::base::unique_fd fd_; +  std::string error_; +  std::unique_ptr<uint8_t[]> buffer_; +  size_t buffer_capacity_; +  size_t buffer_offset_; +  size_t buffer_size_; +  size_t total_byte_count_; +}; + +}  // namespace io +}  // namespace aapt + +#endif  // AAPT_IO_FILEINPUTSTREAM_H diff --git a/tools/aapt2/io/FileInputStream_test.cpp b/tools/aapt2/io/FileInputStream_test.cpp new file mode 100644 index 000000000000..7314ab7beeba --- /dev/null +++ b/tools/aapt2/io/FileInputStream_test.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "io/FileInputStream.h" + +#include "android-base/macros.h" +#include "android-base/test_utils.h" + +#include "test/Test.h" + +using ::android::StringPiece; +using ::testing::Eq; +using ::testing::NotNull; +using ::testing::StrEq; + +namespace aapt { +namespace io { + +TEST(FileInputStreamTest, NextAndBackup) { +  std::string input = "this is a cool string"; +  TemporaryFile file; +  ASSERT_THAT(TEMP_FAILURE_RETRY(write(file.fd, input.c_str(), input.size())), Eq(21)); +  lseek64(file.fd, 0, SEEK_SET); + +  // Use a small buffer size so that we can call Next() a few times. +  FileInputStream in(file.fd, 10u); +  ASSERT_FALSE(in.HadError()); +  EXPECT_THAT(in.ByteCount(), Eq(0u)); + +  const char* buffer; +  size_t size; +  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)) << in.GetError(); +  ASSERT_THAT(size, Eq(10u)); +  ASSERT_THAT(buffer, NotNull()); +  EXPECT_THAT(in.ByteCount(), Eq(10u)); +  EXPECT_THAT(StringPiece(buffer, size), Eq("this is a ")); + +  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); +  ASSERT_THAT(size, Eq(10u)); +  ASSERT_THAT(buffer, NotNull()); +  EXPECT_THAT(in.ByteCount(), Eq(20u)); +  EXPECT_THAT(StringPiece(buffer, size), Eq("cool strin")); + +  in.BackUp(5u); +  EXPECT_THAT(in.ByteCount(), Eq(15u)); + +  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); +  ASSERT_THAT(size, Eq(5u)); +  ASSERT_THAT(buffer, NotNull()); +  ASSERT_THAT(in.ByteCount(), Eq(20u)); +  EXPECT_THAT(StringPiece(buffer, size), Eq("strin")); + +  // Backup 1 more than possible. Should clamp. +  in.BackUp(11u); +  EXPECT_THAT(in.ByteCount(), Eq(10u)); + +  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); +  ASSERT_THAT(size, Eq(10u)); +  ASSERT_THAT(buffer, NotNull()); +  ASSERT_THAT(in.ByteCount(), Eq(20u)); +  EXPECT_THAT(StringPiece(buffer, size), Eq("cool strin")); + +  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); +  ASSERT_THAT(size, Eq(1u)); +  ASSERT_THAT(buffer, NotNull()); +  ASSERT_THAT(in.ByteCount(), Eq(21u)); +  EXPECT_THAT(StringPiece(buffer, size), Eq("g")); + +  EXPECT_FALSE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); +  EXPECT_FALSE(in.HadError()); +} + +}  // namespace io +}  // namespace aapt diff --git a/tools/aapt2/io/StringInputStream.cpp b/tools/aapt2/io/StringInputStream.cpp new file mode 100644 index 000000000000..51a18a7d8a9f --- /dev/null +++ b/tools/aapt2/io/StringInputStream.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "io/StringInputStream.h" + +using ::android::StringPiece; + +namespace aapt { +namespace io { + +StringInputStream::StringInputStream(const StringPiece& str) : str_(str), offset_(0u) { +} + +bool StringInputStream::Next(const void** data, size_t* size) { +  if (offset_ == str_.size()) { +    return false; +  } + +  *data = str_.data() + offset_; +  *size = str_.size() - offset_; +  offset_ = str_.size(); +  return true; +} + +void StringInputStream::BackUp(size_t count) { +  if (count > offset_) { +    count = offset_; +  } +  offset_ -= count; +} + +size_t StringInputStream::ByteCount() const { +  return offset_; +} + +}  // namespace io +}  // namespace aapt diff --git a/tools/aapt2/io/StringInputStream.h b/tools/aapt2/io/StringInputStream.h new file mode 100644 index 000000000000..ff5b112ef274 --- /dev/null +++ b/tools/aapt2/io/StringInputStream.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_IO_STRINGINPUTSTREAM_H +#define AAPT_IO_STRINGINPUTSTREAM_H + +#include "io/Io.h" + +#include "android-base/macros.h" +#include "androidfw/StringPiece.h" + +namespace aapt { +namespace io { + +class StringInputStream : public InputStream { + public: +  explicit StringInputStream(const android::StringPiece& str); + +  bool Next(const void** data, size_t* size) override; + +  void BackUp(size_t count) override; + +  size_t ByteCount() const override; + +  inline bool HadError() const override { +    return false; +  } + +  inline std::string GetError() const override { +    return {}; +  } + + private: +  DISALLOW_COPY_AND_ASSIGN(StringInputStream); + +  android::StringPiece str_; +  size_t offset_; +}; + +}  // namespace io +}  // namespace aapt + +#endif  // AAPT_IO_STRINGINPUTSTREAM_H diff --git a/tools/aapt2/io/StringInputStream_test.cpp b/tools/aapt2/io/StringInputStream_test.cpp new file mode 100644 index 000000000000..cc57bc498313 --- /dev/null +++ b/tools/aapt2/io/StringInputStream_test.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "io/StringInputStream.h" + +#include "test/Test.h" + +using ::android::StringPiece; +using ::testing::Eq; +using ::testing::NotNull; +using ::testing::StrEq; + +namespace aapt { +namespace io { + +TEST(StringInputStreamTest, OneCallToNextShouldReturnEntireBuffer) { +  constexpr const size_t kCount = 1000; +  std::string input; +  input.resize(kCount, 0x7f); +  input[0] = 0x00; +  input[kCount - 1] = 0xff; +  StringInputStream in(input); + +  const char* buffer; +  size_t size; +  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); +  ASSERT_THAT(size, Eq(kCount)); +  ASSERT_THAT(buffer, NotNull()); + +  EXPECT_THAT(buffer[0], Eq(0x00)); +  EXPECT_THAT(buffer[kCount - 1], Eq('\xff')); + +  EXPECT_FALSE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); +  EXPECT_FALSE(in.HadError()); +} + +TEST(StringInputStreamTest, BackUp) { +  std::string input = "hello this is a string"; +  StringInputStream in(input); + +  const char* buffer; +  size_t size; +  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); +  ASSERT_THAT(size, Eq(input.size())); +  ASSERT_THAT(buffer, NotNull()); +  EXPECT_THAT(in.ByteCount(), Eq(input.size())); + +  in.BackUp(6u); +  EXPECT_THAT(in.ByteCount(), Eq(input.size() - 6u)); + +  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); +  ASSERT_THAT(size, Eq(6u)); +  ASSERT_THAT(buffer, NotNull()); +  ASSERT_THAT(buffer, StrEq("string")); +  EXPECT_THAT(in.ByteCount(), Eq(input.size())); +} + +}  // namespace io +}  // namespace aapt diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp index 0cec9ae407f5..d7508d257db4 100644 --- a/tools/aapt2/java/ClassDefinition.cpp +++ b/tools/aapt2/java/ClassDefinition.cpp @@ -39,6 +39,17 @@ void MethodDefinition::WriteToStream(const StringPiece& prefix, bool final,    *out << prefix << "}";  } +ClassDefinition::Result ClassDefinition::AddMember(std::unique_ptr<ClassMember> member) { +  Result result = Result::kAdded; +  auto iter = members_.find(member); +  if (iter != members_.end()) { +    members_.erase(iter); +    result = Result::kOverridden; +  } +  members_.insert(std::move(member)); +  return result; +} +  bool ClassDefinition::empty() const {    for (const std::unique_ptr<ClassMember>& member : members_) {      if (!member->empty()) { diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h index ca76421390d6..6c4bcad83d2a 100644 --- a/tools/aapt2/java/ClassDefinition.h +++ b/tools/aapt2/java/ClassDefinition.h @@ -18,6 +18,7 @@  #define AAPT_JAVA_CLASSDEFINITION_H  #include <ostream> +#include <set>  #include <string>  #include "android-base/macros.h" @@ -37,10 +38,14 @@ class ClassMember {   public:    virtual ~ClassMember() = default; -  AnnotationProcessor* GetCommentBuilder() { return &processor_; } +  AnnotationProcessor* GetCommentBuilder() { +    return &processor_; +  }    virtual bool empty() const = 0; +  virtual const std::string& GetName() const = 0; +    // Writes the class member to the out stream. Subclasses should derive this method    // to write their own data. Call this base method from the subclass to write out    // this member's comments/annotations. @@ -57,7 +62,13 @@ class PrimitiveMember : public ClassMember {    PrimitiveMember(const android::StringPiece& name, const T& val)        : name_(name.to_string()), val_(val) {} -  bool empty() const override { return false; } +  bool empty() const override { +    return false; +  } + +  const std::string& GetName() const override { +    return name_; +  }    void WriteToStream(const android::StringPiece& prefix, bool final,                       std::ostream* out) const override { @@ -83,7 +94,13 @@ class PrimitiveMember<std::string> : public ClassMember {    PrimitiveMember(const android::StringPiece& name, const std::string& val)        : name_(name.to_string()), val_(val) {} -  bool empty() const override { return false; } +  bool empty() const override { +    return false; +  } + +  const std::string& GetName() const override { +    return name_; +  }    void WriteToStream(const android::StringPiece& prefix, bool final,                       std::ostream* out) const override { @@ -109,9 +126,17 @@ class PrimitiveArrayMember : public ClassMember {   public:    explicit PrimitiveArrayMember(const android::StringPiece& name) : name_(name.to_string()) {} -  void AddElement(const T& val) { elements_.push_back(val); } +  void AddElement(const T& val) { +    elements_.push_back(val); +  } -  bool empty() const override { return false; } +  bool empty() const override { +    return false; +  } + +  const std::string& GetName() const override { +    return name_; +  }    void WriteToStream(const android::StringPiece& prefix, bool final,                       std::ostream* out) const override { @@ -154,6 +179,11 @@ class MethodDefinition : public ClassMember {    // formatting may be broken.    void AppendStatement(const android::StringPiece& statement); +  // Not quite the same as a name, but good enough. +  const std::string& GetName() const override { +    return signature_; +  } +    // Even if the method is empty, we always want to write the method signature.    bool empty() const override { return false; } @@ -175,19 +205,34 @@ class ClassDefinition : public ClassMember {    ClassDefinition(const android::StringPiece& name, ClassQualifier qualifier, bool createIfEmpty)        : name_(name.to_string()), qualifier_(qualifier), create_if_empty_(createIfEmpty) {} -  void AddMember(std::unique_ptr<ClassMember> member) { -    members_.push_back(std::move(member)); -  } +  enum class Result { +    kAdded, +    kOverridden, +  }; + +  Result AddMember(std::unique_ptr<ClassMember> member);    bool empty() const override; + +  const std::string& GetName() const override { +    return name_; +  } +    void WriteToStream(const android::StringPiece& prefix, bool final,                       std::ostream* out) const override;   private: +  struct ClassMemberCompare { +    using T = std::unique_ptr<ClassMember>; +    bool operator()(const T& a, const T& b) const { +      return a->GetName() < b->GetName(); +    } +  }; +    std::string name_;    ClassQualifier qualifier_;    bool create_if_empty_; -  std::vector<std::unique_ptr<ClassMember>> members_; +  std::set<std::unique_ptr<ClassMember>, ClassMemberCompare> members_;    DISALLOW_COPY_AND_ASSIGN(ClassDefinition);  }; diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp index f49e4985fcf1..cad4c6c7c94f 100644 --- a/tools/aapt2/java/ManifestClassGenerator.cpp +++ b/tools/aapt2/java/ManifestClassGenerator.cpp @@ -81,7 +81,10 @@ static bool WriteSymbol(const Source& source, IDiagnostics* diag,        util::make_unique<StringMember>(result.value(), attr->value);    string_member->GetCommentBuilder()->AppendComment(el->comment); -  class_def->AddMember(std::move(string_member)); +  if (class_def->AddMember(std::move(string_member)) == ClassDefinition::Result::kOverridden) { +    diag->Warn(DiagMessage(source.WithLine(el->line_number)) +               << "duplicate definitions of '" << result.value() << "', overriding previous"); +  }    return true;  } diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp index 9f6ec210a6a7..44b6a1ffd5ae 100644 --- a/tools/aapt2/java/ManifestClassGenerator_test.cpp +++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp @@ -112,6 +112,21 @@ TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) {    EXPECT_THAT(actual, HasSubstr(expected_secret));  } +// This is bad but part of public API behaviour so we need to preserve it. +TEST(ManifestClassGeneratorTest, LastSeenPermissionWithSameLeafNameTakesPrecedence) { +  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); +  std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"( +      <manifest xmlns:android="http://schemas.android.com/apk/res/android"> +        <permission android:name="android.permission.ACCESS_INTERNET" /> +        <permission android:name="com.android.aapt.test.ACCESS_INTERNET" /> +      </manifest>)"); + +  std::string actual; +  ASSERT_TRUE(GetManifestClassText(context.get(), manifest.get(), &actual)); +  EXPECT_THAT(actual, HasSubstr("ACCESS_INTERNET=\"com.android.aapt.test.ACCESS_INTERNET\";")); +  EXPECT_THAT(actual, Not(HasSubstr("ACCESS_INTERNET=\"android.permission.ACCESS_INTERNET\";"))); +} +  static ::testing::AssertionResult GetManifestClassText(IAaptContext* context, xml::XmlResource* res,                                                         std::string* out_str) {    std::unique_ptr<ClassDefinition> manifest_class = diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp index 5f61faeeebe7..10c46101123c 100644 --- a/tools/aapt2/java/ProguardRules.cpp +++ b/tools/aapt2/java/ProguardRules.cpp @@ -29,18 +29,12 @@ namespace proguard {  class BaseVisitor : public xml::Visitor {   public: -  BaseVisitor(const Source& source, KeepSet* keep_set) -      : source_(source), keep_set_(keep_set) {} +  using xml::Visitor::Visit; -  virtual void Visit(xml::Text*) override{}; - -  virtual void Visit(xml::Namespace* node) override { -    for (const auto& child : node->children) { -      child->Accept(this); -    } +  BaseVisitor(const Source& source, KeepSet* keep_set) : source_(source), keep_set_(keep_set) {    } -  virtual void Visit(xml::Element* node) override { +  void Visit(xml::Element* node) override {      if (!node->namespace_uri.empty()) {        Maybe<xml::ExtractedPackage> maybe_package =            xml::ExtractPackageFromNamespace(node->namespace_uri); @@ -78,10 +72,10 @@ class BaseVisitor : public xml::Visitor {  class LayoutVisitor : public BaseVisitor {   public: -  LayoutVisitor(const Source& source, KeepSet* keep_set) -      : BaseVisitor(source, keep_set) {} +  LayoutVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { +  } -  virtual void Visit(xml::Element* node) override { +  void Visit(xml::Element* node) override {      bool check_class = false;      bool check_name = false;      if (node->namespace_uri.empty()) { @@ -119,7 +113,7 @@ class MenuVisitor : public BaseVisitor {    MenuVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) {    } -  virtual void Visit(xml::Element* node) override { +  void Visit(xml::Element* node) override {      if (node->namespace_uri.empty() && node->name == "item") {        for (const auto& attr : node->attributes) {          if (attr.namespace_uri == xml::kSchemaAndroid) { @@ -142,10 +136,10 @@ class MenuVisitor : public BaseVisitor {  class XmlResourceVisitor : public BaseVisitor {   public: -  XmlResourceVisitor(const Source& source, KeepSet* keep_set) -      : BaseVisitor(source, keep_set) {} +  XmlResourceVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { +  } -  virtual void Visit(xml::Element* node) override { +  void Visit(xml::Element* node) override {      bool check_fragment = false;      if (node->namespace_uri.empty()) {        check_fragment = @@ -169,13 +163,12 @@ class XmlResourceVisitor : public BaseVisitor {  class TransitionVisitor : public BaseVisitor {   public: -  TransitionVisitor(const Source& source, KeepSet* keep_set) -      : BaseVisitor(source, keep_set) {} +  TransitionVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { +  } -  virtual void Visit(xml::Element* node) override { +  void Visit(xml::Element* node) override {      bool check_class = -        node->namespace_uri.empty() && -        (node->name == "transition" || node->name == "pathMotion"); +        node->namespace_uri.empty() && (node->name == "transition" || node->name == "pathMotion");      if (check_class) {        xml::Attribute* attr = node->FindAttribute({}, "class");        if (attr && util::IsJavaClassName(attr->value)) { @@ -195,7 +188,7 @@ class ManifestVisitor : public BaseVisitor {    ManifestVisitor(const Source& source, KeepSet* keep_set, bool main_dex_only)        : BaseVisitor(source, keep_set), main_dex_only_(main_dex_only) {} -  virtual void Visit(xml::Element* node) override { +  void Visit(xml::Element* node) override {      if (node->namespace_uri.empty()) {        bool get_name = false;        if (node->name == "manifest") { @@ -205,18 +198,15 @@ class ManifestVisitor : public BaseVisitor {          }        } else if (node->name == "application") {          get_name = true; -        xml::Attribute* attr = -            node->FindAttribute(xml::kSchemaAndroid, "backupAgent"); +        xml::Attribute* attr = node->FindAttribute(xml::kSchemaAndroid, "backupAgent");          if (attr) { -          Maybe<std::string> result = -              util::GetFullyQualifiedClassName(package_, attr->value); +          Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value);            if (result) {              AddClass(node->line_number, result.value());            }          }          if (main_dex_only_) { -          xml::Attribute* default_process = -              node->FindAttribute(xml::kSchemaAndroid, "process"); +          xml::Attribute* default_process = node->FindAttribute(xml::kSchemaAndroid, "process");            if (default_process) {              default_process_ = default_process->value;            } @@ -226,8 +216,7 @@ class ManifestVisitor : public BaseVisitor {          get_name = true;          if (main_dex_only_) { -          xml::Attribute* component_process = -              node->FindAttribute(xml::kSchemaAndroid, "process"); +          xml::Attribute* component_process = node->FindAttribute(xml::kSchemaAndroid, "process");            const std::string& process =                component_process ? component_process->value : default_process_; @@ -242,8 +231,7 @@ class ManifestVisitor : public BaseVisitor {          get_name = attr != nullptr;          if (get_name) { -          Maybe<std::string> result = -              util::GetFullyQualifiedClassName(package_, attr->value); +          Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value);            if (result) {              AddClass(node->line_number, result.value());            } @@ -261,8 +249,7 @@ class ManifestVisitor : public BaseVisitor {    std::string default_process_;  }; -bool CollectProguardRulesForManifest(const Source& source, -                                     xml::XmlResource* res, KeepSet* keep_set, +bool CollectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keep_set,                                       bool main_dex_only) {    ManifestVisitor visitor(source, keep_set, main_dex_only);    if (res->root) { @@ -272,8 +259,7 @@ bool CollectProguardRulesForManifest(const Source& source,    return false;  } -bool CollectProguardRules(const Source& source, xml::XmlResource* res, -                          KeepSet* keep_set) { +bool CollectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keep_set) {    if (!res->root) {      return false;    } @@ -321,8 +307,7 @@ bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set) {      for (const Source& source : entry.second) {        *out << "# Referenced at " << source << "\n";      } -    *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" -         << std::endl; +    *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl;    }    return true;  } diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp index 80edb352f42c..da7f410b8b08 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -122,7 +122,7 @@ TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {    xml::Element* el;    xml::Attribute* attr; -  el = xml::FindRootElement(doc.get()); +  el = doc->root.get();    ASSERT_NE(nullptr, el);    el = el->FindChild({}, "uses-sdk");    ASSERT_NE(nullptr, el); @@ -141,7 +141,7 @@ TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {                            options);    ASSERT_NE(nullptr, doc); -  el = xml::FindRootElement(doc.get()); +  el = doc->root.get();    ASSERT_NE(nullptr, el);    el = el->FindChild({}, "uses-sdk");    ASSERT_NE(nullptr, el); @@ -160,7 +160,7 @@ TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {                            options);    ASSERT_NE(nullptr, doc); -  el = xml::FindRootElement(doc.get()); +  el = doc->root.get();    ASSERT_NE(nullptr, el);    el = el->FindChild({}, "uses-sdk");    ASSERT_NE(nullptr, el); @@ -177,7 +177,7 @@ TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {                            options);    ASSERT_NE(nullptr, doc); -  el = xml::FindRootElement(doc.get()); +  el = doc->root.get();    ASSERT_NE(nullptr, el);    el = el->FindChild({}, "uses-sdk");    ASSERT_NE(nullptr, el); @@ -199,7 +199,7 @@ TEST_F(ManifestFixerTest, UsesSdkMustComeBeforeApplication) {                                                              options);    ASSERT_NE(nullptr, doc); -  xml::Element* manifest_el = xml::FindRootElement(doc.get()); +  xml::Element* manifest_el = doc->root.get();    ASSERT_NE(nullptr, manifest_el);    ASSERT_EQ("manifest", manifest_el->name); @@ -248,7 +248,7 @@ TEST_F(ManifestFixerTest, RenameManifestPackageAndFullyQualifyClasses) {                                                              options);    ASSERT_NE(nullptr, doc); -  xml::Element* manifestEl = xml::FindRootElement(doc.get()); +  xml::Element* manifestEl = doc->root.get();    ASSERT_NE(nullptr, manifestEl);    xml::Attribute* attr = nullptr; @@ -297,7 +297,7 @@ TEST_F(ManifestFixerTest,                                                              options);    ASSERT_NE(nullptr, doc); -  xml::Element* manifest_el = xml::FindRootElement(doc.get()); +  xml::Element* manifest_el = doc->root.get();    ASSERT_NE(nullptr, manifest_el);    xml::Element* instrumentation_el = @@ -321,7 +321,7 @@ TEST_F(ManifestFixerTest, UseDefaultVersionNameAndCode) {                                                              options);    ASSERT_NE(nullptr, doc); -  xml::Element* manifest_el = xml::FindRootElement(doc.get()); +  xml::Element* manifest_el = doc->root.get();    ASSERT_NE(nullptr, manifest_el);    xml::Attribute* attr = @@ -344,7 +344,7 @@ TEST_F(ManifestFixerTest, EnsureManifestAttributesAreTyped) {        Verify("<manifest package=\"android\" coreApp=\"true\" />");    ASSERT_NE(nullptr, doc); -  xml::Element* el = xml::FindRootElement(doc.get()); +  xml::Element* el = doc->root.get();    ASSERT_NE(nullptr, el);    EXPECT_EQ("manifest", el->name); diff --git a/tools/aapt2/link/XmlCompatVersioner.cpp b/tools/aapt2/link/XmlCompatVersioner.cpp index f1f4e3b7f05f..20ebdc696814 100644 --- a/tools/aapt2/link/XmlCompatVersioner.cpp +++ b/tools/aapt2/link/XmlCompatVersioner.cpp @@ -107,7 +107,7 @@ std::unique_ptr<xml::XmlResource> XmlCompatVersioner::ProcessDoc(    std::unique_ptr<xml::XmlResource> cloned_doc = util::make_unique<xml::XmlResource>(doc->file);    cloned_doc->file.config.sdkVersion = static_cast<uint16_t>(target_api); -  cloned_doc->root = doc->root->Clone([&](const xml::Element& el, xml::Element* out_el) { +  cloned_doc->root = doc->root->CloneElement([&](const xml::Element& el, xml::Element* out_el) {      for (const auto& attr : el.attributes) {        if (!attr.compiled_attribute) {          // Just copy if this isn't a compiled attribute. diff --git a/tools/aapt2/link/XmlCompatVersioner_test.cpp b/tools/aapt2/link/XmlCompatVersioner_test.cpp index ce6605c6ad97..29ad25f79ab9 100644 --- a/tools/aapt2/link/XmlCompatVersioner_test.cpp +++ b/tools/aapt2/link/XmlCompatVersioner_test.cpp @@ -19,6 +19,12 @@  #include "Linkers.h"  #include "test/Test.h" +using ::aapt::test::ValueEq; +using ::testing::Eq; +using ::testing::IsNull; +using ::testing::NotNull; +using ::testing::SizeIs; +  namespace aapt {  constexpr auto TYPE_DIMENSION = android::ResTable_map::TYPE_DIMENSION; @@ -68,12 +74,12 @@ class XmlCompatVersionerTest : public ::testing::Test {  };  TEST_F(XmlCompatVersionerTest, NoRulesOnlyStripsAndCopies) { -  auto doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( +  auto doc = test::BuildXmlDomForPackageName(context_.get(), R"(        <View xmlns:android="http://schemas.android.com/apk/res/android"            xmlns:app="http://schemas.android.com/apk/res-auto"            android:paddingHorizontal="24dp"            app:foo="16dp" -          foo="bar"/>)EOF"); +          foo="bar"/>)");    XmlReferenceLinker linker;    ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); @@ -84,35 +90,35 @@ TEST_F(XmlCompatVersionerTest, NoRulesOnlyStripsAndCopies) {    XmlCompatVersioner versioner(&rules);    std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs =        versioner.Process(context_.get(), doc.get(), api_range); -  ASSERT_EQ(2u, versioned_docs.size()); +  ASSERT_THAT(versioned_docs, SizeIs(2u));    xml::Element* el;    // Source XML file's sdkVersion == 0, so the first one must also have the same sdkVersion. -  EXPECT_EQ(static_cast<uint16_t>(0), versioned_docs[0]->file.config.sdkVersion); -  el = xml::FindRootElement(versioned_docs[0].get()); -  ASSERT_NE(nullptr, el); -  EXPECT_EQ(2u, el->attributes.size()); -  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); -  EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAuto, "foo")); -  EXPECT_NE(nullptr, el->FindAttribute({}, "foo")); - -  EXPECT_EQ(static_cast<uint16_t>(SDK_LOLLIPOP_MR1), versioned_docs[1]->file.config.sdkVersion); -  el = xml::FindRootElement(versioned_docs[1].get()); -  ASSERT_NE(nullptr, el); -  EXPECT_EQ(3u, el->attributes.size()); -  EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); -  EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAuto, "foo")); -  EXPECT_NE(nullptr, el->FindAttribute({}, "foo")); +  EXPECT_THAT(versioned_docs[0]->file.config.sdkVersion, Eq(0u)); +  el = versioned_docs[0]->root.get(); +  ASSERT_THAT(el, NotNull()); +  EXPECT_THAT(el->attributes, SizeIs(2u)); +  EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"), IsNull()); +  EXPECT_THAT(el->FindAttribute(xml::kSchemaAuto, "foo"), NotNull()); +  EXPECT_THAT(el->FindAttribute({}, "foo"), NotNull()); + +  EXPECT_THAT(versioned_docs[1]->file.config.sdkVersion, Eq(SDK_LOLLIPOP_MR1)); +  el = versioned_docs[1]->root.get(); +  ASSERT_THAT(el, NotNull()); +  EXPECT_THAT(el->attributes, SizeIs(3u)); +  EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"), NotNull()); +  EXPECT_THAT(el->FindAttribute(xml::kSchemaAuto, "foo"), NotNull()); +  EXPECT_THAT(el->FindAttribute({}, "foo"), NotNull());  }  TEST_F(XmlCompatVersionerTest, SingleRule) { -  auto doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( +  auto doc = test::BuildXmlDomForPackageName(context_.get(), R"(        <View xmlns:android="http://schemas.android.com/apk/res/android"            xmlns:app="http://schemas.android.com/apk/res-auto"            android:paddingHorizontal="24dp"            app:foo="16dp" -          foo="bar"/>)EOF"); +          foo="bar"/>)");    XmlReferenceLinker linker;    ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); @@ -129,51 +135,51 @@ TEST_F(XmlCompatVersionerTest, SingleRule) {    XmlCompatVersioner versioner(&rules);    std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs =        versioner.Process(context_.get(), doc.get(), api_range); -  ASSERT_EQ(2u, versioned_docs.size()); +  ASSERT_THAT(versioned_docs, SizeIs(2u));    xml::Element* el; -  EXPECT_EQ(static_cast<uint16_t>(0), versioned_docs[0]->file.config.sdkVersion); -  el = xml::FindRootElement(versioned_docs[0].get()); -  ASSERT_NE(nullptr, el); -  EXPECT_EQ(4u, el->attributes.size()); -  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); -  EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAuto, "foo")); -  EXPECT_NE(nullptr, el->FindAttribute({}, "foo")); +  EXPECT_THAT(versioned_docs[0]->file.config.sdkVersion, Eq(0u)); +  el = versioned_docs[0]->root.get(); +  ASSERT_THAT(el, NotNull()); +  EXPECT_THAT(el->attributes, SizeIs(4u)); +  EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"), IsNull()); +  EXPECT_THAT(el->FindAttribute(xml::kSchemaAuto, "foo"), NotNull()); +  EXPECT_THAT(el->FindAttribute({}, "foo"), NotNull());    xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"); -  ASSERT_NE(nullptr, attr); -  ASSERT_NE(nullptr, attr->compiled_value); +  ASSERT_THAT(attr, NotNull()); +  ASSERT_THAT(attr->compiled_value, NotNull());    ASSERT_TRUE(attr->compiled_attribute);    attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight"); -  ASSERT_NE(nullptr, attr); -  ASSERT_NE(nullptr, attr->compiled_value); +  ASSERT_THAT(attr, NotNull()); +  ASSERT_THAT(attr->compiled_value, NotNull());    ASSERT_TRUE(attr->compiled_attribute); -  EXPECT_EQ(static_cast<uint16_t>(SDK_LOLLIPOP_MR1), versioned_docs[1]->file.config.sdkVersion); -  el = xml::FindRootElement(versioned_docs[1].get()); -  ASSERT_NE(nullptr, el); -  EXPECT_EQ(5u, el->attributes.size()); -  EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); -  EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAuto, "foo")); -  EXPECT_NE(nullptr, el->FindAttribute({}, "foo")); +  EXPECT_THAT(versioned_docs[1]->file.config.sdkVersion, Eq(SDK_LOLLIPOP_MR1)); +  el = versioned_docs[1]->root.get(); +  ASSERT_THAT(el, NotNull()); +  EXPECT_THAT(el->attributes, SizeIs(5u)); +  EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"), NotNull()); +  EXPECT_THAT(el->FindAttribute(xml::kSchemaAuto, "foo"), NotNull()); +  EXPECT_THAT(el->FindAttribute({}, "foo"), NotNull());    attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"); -  ASSERT_NE(nullptr, attr); -  ASSERT_NE(nullptr, attr->compiled_value); +  ASSERT_THAT(attr, NotNull()); +  ASSERT_THAT(attr->compiled_value, NotNull());    ASSERT_TRUE(attr->compiled_attribute);    attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight"); -  ASSERT_NE(nullptr, attr); -  ASSERT_NE(nullptr, attr->compiled_value); +  ASSERT_THAT(attr, NotNull()); +  ASSERT_THAT(attr->compiled_value, NotNull());    ASSERT_TRUE(attr->compiled_attribute);  }  TEST_F(XmlCompatVersionerTest, ChainedRule) { -  auto doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( +  auto doc = test::BuildXmlDomForPackageName(context_.get(), R"(        <View xmlns:android="http://schemas.android.com/apk/res/android" -          android:paddingHorizontal="24dp" />)EOF"); +          android:paddingHorizontal="24dp" />)");    XmlReferenceLinker linker;    ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); @@ -193,71 +199,70 @@ TEST_F(XmlCompatVersionerTest, ChainedRule) {    XmlCompatVersioner versioner(&rules);    std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs =        versioner.Process(context_.get(), doc.get(), api_range); -  ASSERT_EQ(3u, versioned_docs.size()); +  ASSERT_THAT(versioned_docs, SizeIs(3u));    xml::Element* el; -  EXPECT_EQ(static_cast<uint16_t>(0), versioned_docs[0]->file.config.sdkVersion); -  el = xml::FindRootElement(versioned_docs[0].get()); -  ASSERT_NE(nullptr, el); -  EXPECT_EQ(2u, el->attributes.size()); -  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); +  EXPECT_THAT(versioned_docs[0]->file.config.sdkVersion, Eq(0u)); +  el = versioned_docs[0]->root.get(); +  ASSERT_THAT(el, NotNull()); +  EXPECT_THAT(el->attributes, SizeIs(2u)); +  EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"), IsNull());    xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"); -  ASSERT_NE(nullptr, attr); -  ASSERT_NE(nullptr, attr->compiled_value); +  ASSERT_THAT(attr, NotNull()); +  ASSERT_THAT(attr->compiled_value, NotNull());    ASSERT_TRUE(attr->compiled_attribute);    attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight"); -  ASSERT_NE(nullptr, attr); -  ASSERT_NE(nullptr, attr->compiled_value); +  ASSERT_THAT(attr, NotNull()); +  ASSERT_THAT(attr->compiled_value, NotNull());    ASSERT_TRUE(attr->compiled_attribute); -  EXPECT_EQ(static_cast<uint16_t>(SDK_HONEYCOMB), versioned_docs[1]->file.config.sdkVersion); -  el = xml::FindRootElement(versioned_docs[1].get()); -  ASSERT_NE(nullptr, el); -  EXPECT_EQ(1u, el->attributes.size()); -  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); -  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingLeft")); -  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingRight")); +  EXPECT_THAT(versioned_docs[1]->file.config.sdkVersion, Eq(SDK_HONEYCOMB)); +  el = versioned_docs[1]->root.get(); +  ASSERT_THAT(el, NotNull()); +  EXPECT_THAT(el->attributes, SizeIs(1u)); +  EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"), IsNull()); +  EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"), IsNull()); +  EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingRight"), IsNull());    attr = el->FindAttribute(xml::kSchemaAndroid, "progressBarPadding"); -  ASSERT_NE(nullptr, attr); -  ASSERT_NE(nullptr, attr->compiled_value); +  ASSERT_THAT(attr, NotNull()); +  ASSERT_THAT(attr->compiled_value, NotNull());    ASSERT_TRUE(attr->compiled_attribute); -  EXPECT_EQ(static_cast<uint16_t>(SDK_LOLLIPOP_MR1), versioned_docs[2]->file.config.sdkVersion); -  el = xml::FindRootElement(versioned_docs[2].get()); -  ASSERT_NE(nullptr, el); -  EXPECT_EQ(2u, el->attributes.size()); -  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingLeft")); -  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingRight")); +  EXPECT_THAT(versioned_docs[2]->file.config.sdkVersion, Eq(SDK_LOLLIPOP_MR1)); +  el = versioned_docs[2]->root.get(); +  ASSERT_THAT(el, NotNull()); +  EXPECT_THAT(el->attributes, SizeIs(2u)); +  EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"), IsNull()); +  EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingRight"), IsNull());    attr = el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"); -  ASSERT_NE(nullptr, attr); -  ASSERT_NE(nullptr, attr->compiled_value); +  ASSERT_THAT(attr, NotNull()); +  ASSERT_THAT(attr->compiled_value, NotNull());    ASSERT_TRUE(attr->compiled_attribute);    attr = el->FindAttribute(xml::kSchemaAndroid, "progressBarPadding"); -  ASSERT_NE(nullptr, attr); -  ASSERT_NE(nullptr, attr->compiled_value); +  ASSERT_THAT(attr, NotNull()); +  ASSERT_THAT(attr->compiled_value, NotNull());    ASSERT_TRUE(attr->compiled_attribute);  }  TEST_F(XmlCompatVersionerTest, DegradeRuleOverridesExistingAttribute) { -  auto doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( +  auto doc = test::BuildXmlDomForPackageName(context_.get(), R"(        <View xmlns:android="http://schemas.android.com/apk/res/android"            android:paddingHorizontal="24dp"            android:paddingLeft="16dp" -          android:paddingRight="16dp"/>)EOF"); +          android:paddingRight="16dp"/>)");    XmlReferenceLinker linker;    ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); -  Item* padding_horizontal_value = xml::FindRootElement(doc.get()) -                                       ->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal") -                                       ->compiled_value.get(); -  ASSERT_NE(nullptr, padding_horizontal_value); +  Item* padding_horizontal_value = +      doc->root->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")->compiled_value.get(); +  ASSERT_THAT(padding_horizontal_value, NotNull());    XmlCompatVersioner::Rules rules;    rules[R::attr::paddingHorizontal] = @@ -271,50 +276,50 @@ TEST_F(XmlCompatVersionerTest, DegradeRuleOverridesExistingAttribute) {    XmlCompatVersioner versioner(&rules);    std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs =        versioner.Process(context_.get(), doc.get(), api_range); -  ASSERT_EQ(2u, versioned_docs.size()); +  ASSERT_THAT(versioned_docs, SizeIs(2u));    xml::Element* el; -  EXPECT_EQ(static_cast<uint16_t>(0), versioned_docs[0]->file.config.sdkVersion); -  el = xml::FindRootElement(versioned_docs[0].get()); -  ASSERT_NE(nullptr, el); -  EXPECT_EQ(2u, el->attributes.size()); -  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); +  EXPECT_THAT(versioned_docs[0]->file.config.sdkVersion, Eq(0u)); +  el = versioned_docs[0]->root.get(); +  ASSERT_THAT(el, NotNull()); +  EXPECT_THAT(el->attributes, SizeIs(2u)); +  EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"), IsNull());    xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"); -  ASSERT_NE(nullptr, attr); -  ASSERT_NE(nullptr, attr->compiled_value); -  ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get())); +  ASSERT_THAT(attr, NotNull()); +  ASSERT_THAT(attr->compiled_value, NotNull());    ASSERT_TRUE(attr->compiled_attribute); +  ASSERT_THAT(*attr->compiled_value, ValueEq(padding_horizontal_value));    attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight"); -  ASSERT_NE(nullptr, attr); -  ASSERT_NE(nullptr, attr->compiled_value); -  ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get())); +  ASSERT_THAT(attr, NotNull()); +  ASSERT_THAT(attr->compiled_value, NotNull());    ASSERT_TRUE(attr->compiled_attribute); +  ASSERT_THAT(*attr->compiled_value, ValueEq(padding_horizontal_value)); -  EXPECT_EQ(static_cast<uint16_t>(SDK_LOLLIPOP_MR1), versioned_docs[1]->file.config.sdkVersion); -  el = xml::FindRootElement(versioned_docs[1].get()); -  ASSERT_NE(nullptr, el); -  EXPECT_EQ(3u, el->attributes.size()); +  EXPECT_THAT(versioned_docs[1]->file.config.sdkVersion, Eq(SDK_LOLLIPOP_MR1)); +  el = versioned_docs[1]->root.get(); +  ASSERT_THAT(el, NotNull()); +  EXPECT_THAT(el->attributes, SizeIs(3u));    attr = el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"); -  ASSERT_NE(nullptr, attr); -  ASSERT_NE(nullptr, attr->compiled_value); -  ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get())); +  ASSERT_THAT(attr, NotNull()); +  ASSERT_THAT(attr->compiled_value, NotNull());    ASSERT_TRUE(attr->compiled_attribute); +  ASSERT_THAT(*attr->compiled_value, ValueEq(padding_horizontal_value));    attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"); -  ASSERT_NE(nullptr, attr); -  ASSERT_NE(nullptr, attr->compiled_value); -  ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get())); +  ASSERT_THAT(attr, NotNull()); +  ASSERT_THAT(attr->compiled_value, NotNull());    ASSERT_TRUE(attr->compiled_attribute); +  ASSERT_THAT(*attr->compiled_value, ValueEq(padding_horizontal_value));    attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight"); -  ASSERT_NE(nullptr, attr); -  ASSERT_NE(nullptr, attr->compiled_value); -  ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get())); +  ASSERT_THAT(attr, NotNull()); +  ASSERT_THAT(attr->compiled_value, NotNull());    ASSERT_TRUE(attr->compiled_attribute); +  ASSERT_THAT(*attr->compiled_value, ValueEq(padding_horizontal_value));  }  }  // namespace aapt diff --git a/tools/aapt2/link/XmlNamespaceRemover.cpp b/tools/aapt2/link/XmlNamespaceRemover.cpp index 24aa5660ae30..b5e2423d58dc 100644 --- a/tools/aapt2/link/XmlNamespaceRemover.cpp +++ b/tools/aapt2/link/XmlNamespaceRemover.cpp @@ -24,37 +24,19 @@ namespace aapt {  namespace { -/** - * Visits each xml Node, removing URI references and nested namespaces. - */ +// Visits each xml Node, removing URI references and nested namespaces.  class XmlVisitor : public xml::Visitor {   public:    explicit XmlVisitor(bool keep_uris) : keep_uris_(keep_uris) {}    void Visit(xml::Element* el) override { -    // Strip namespaces -    for (auto& child : el->children) { -      while (child && xml::NodeCast<xml::Namespace>(child.get())) { -        if (child->children.empty()) { -          child = {}; -        } else { -          child = std::move(child->children.front()); -          child->parent = el; -        } -      } -    } -    el->children.erase( -        std::remove_if(el->children.begin(), el->children.end(), -                       [](const std::unique_ptr<xml::Node>& child) -> bool { -                         return child == nullptr; -                       }), -        el->children.end()); +    el->namespace_decls.clear();      if (!keep_uris_) {        for (xml::Attribute& attr : el->attributes) { -        attr.namespace_uri = std::string(); +        attr.namespace_uri.clear();        } -      el->namespace_uri = std::string(); +      el->namespace_uri.clear();      }      xml::Visitor::Visit(el);    } @@ -67,19 +49,11 @@ class XmlVisitor : public xml::Visitor {  }  // namespace -bool XmlNamespaceRemover::Consume(IAaptContext* context, -                                  xml::XmlResource* resource) { +bool XmlNamespaceRemover::Consume(IAaptContext* context, xml::XmlResource* resource) {    if (!resource->root) {      return false;    } -  // Replace any root namespaces until the root is a non-namespace node -  while (xml::NodeCast<xml::Namespace>(resource->root.get())) { -    if (resource->root->children.empty()) { -      break; -    } -    resource->root = std::move(resource->root->children.front()); -    resource->root->parent = nullptr; -  } +    XmlVisitor visitor(keep_uris_);    resource->root->Accept(&visitor);    return true; diff --git a/tools/aapt2/link/XmlNamespaceRemover_test.cpp b/tools/aapt2/link/XmlNamespaceRemover_test.cpp index a176c03a6432..df4920fde37f 100644 --- a/tools/aapt2/link/XmlNamespaceRemover_test.cpp +++ b/tools/aapt2/link/XmlNamespaceRemover_test.cpp @@ -18,6 +18,10 @@  #include "test/Test.h" +using ::testing::NotNull; +using ::testing::SizeIs; +using ::testing::StrEq; +  namespace aapt {  class XmlUriTestVisitor : public xml::Visitor { @@ -25,16 +29,14 @@ class XmlUriTestVisitor : public xml::Visitor {    XmlUriTestVisitor() = default;    void Visit(xml::Element* el) override { +    EXPECT_THAT(el->namespace_decls, SizeIs(0u)); +      for (const auto& attr : el->attributes) { -      EXPECT_EQ(std::string(), attr.namespace_uri); +      EXPECT_THAT(attr.namespace_uri, StrEq(""));      } -    EXPECT_EQ(std::string(), el->namespace_uri); -    xml::Visitor::Visit(el); -  } +    EXPECT_THAT(el->namespace_uri, StrEq("")); -  void Visit(xml::Namespace* ns) override { -    EXPECT_EQ(std::string(), ns->namespace_uri); -    xml::Visitor::Visit(ns); +    xml::Visitor::Visit(el);    }   private: @@ -45,10 +47,9 @@ class XmlNamespaceTestVisitor : public xml::Visitor {   public:    XmlNamespaceTestVisitor() = default; -  void Visit(xml::Namespace* ns) override { -    ADD_FAILURE() << "Detected namespace: " << ns->namespace_prefix << "=\"" -                  << ns->namespace_uri << "\""; -    xml::Visitor::Visit(ns); +  void Visit(xml::Element* el) override { +    EXPECT_THAT(el->namespace_decls, SizeIs(0u)); +    xml::Visitor::Visit(el);    }   private: @@ -58,8 +59,7 @@ class XmlNamespaceTestVisitor : public xml::Visitor {  class XmlNamespaceRemoverTest : public ::testing::Test {   public:    void SetUp() override { -    context_ = -        test::ContextBuilder().SetCompilationPackage("com.app.test").Build(); +    context_ = test::ContextBuilder().SetCompilationPackage("com.app.test").Build();    }   protected: @@ -75,8 +75,8 @@ TEST_F(XmlNamespaceRemoverTest, RemoveUris) {    XmlNamespaceRemover remover;    ASSERT_TRUE(remover.Consume(context_.get(), doc.get())); -  xml::Node* root = doc.get()->root.get(); -  ASSERT_NE(root, nullptr); +  xml::Node* root = doc->root.get(); +  ASSERT_THAT(root, NotNull());    XmlUriTestVisitor visitor;    root->Accept(&visitor); @@ -93,8 +93,8 @@ TEST_F(XmlNamespaceRemoverTest, RemoveNamespaces) {    XmlNamespaceRemover remover;    ASSERT_TRUE(remover.Consume(context_.get(), doc.get())); -  xml::Node* root = doc.get()->root.get(); -  ASSERT_NE(root, nullptr); +  xml::Node* root = doc->root.get(); +  ASSERT_THAT(root, NotNull());    XmlNamespaceTestVisitor visitor;    root->Accept(&visitor); @@ -112,8 +112,8 @@ TEST_F(XmlNamespaceRemoverTest, RemoveNestedNamespaces) {    XmlNamespaceRemover remover;    ASSERT_TRUE(remover.Consume(context_.get(), doc.get())); -  xml::Node* root = doc.get()->root.get(); -  ASSERT_NE(root, nullptr); +  xml::Node* root = doc->root.get(); +  ASSERT_THAT(root, NotNull());    XmlNamespaceTestVisitor visitor;    root->Accept(&visitor); diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp index 721fc26399bc..bcecd2003846 100644 --- a/tools/aapt2/link/XmlReferenceLinker.cpp +++ b/tools/aapt2/link/XmlReferenceLinker.cpp @@ -144,7 +144,9 @@ class XmlVisitor : public xml::PackageAwareVisitor {      xml::PackageAwareVisitor::Visit(el);    } -  bool HasError() { return error_ || reference_visitor_.HasError(); } +  bool HasError() { +    return error_ || reference_visitor_.HasError(); +  }   private:    DISALLOW_COPY_AND_ASSIGN(XmlVisitor); diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp index 228cfb9e3d66..ef99355e5b5f 100644 --- a/tools/aapt2/link/XmlReferenceLinker_test.cpp +++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp @@ -79,20 +79,20 @@ class XmlReferenceLinkerTest : public ::testing::Test {  };  TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) { -  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( -        <View xmlns:android="http://schemas.android.com/apk/res/android" -              android:layout_width="match_parent" -              android:background="@color/green" -              android:text="hello" -              android:attr="\?hello" -              nonAaptAttr="1" -              nonAaptAttrRef="@id/id" -              class="hello" />)EOF"); +  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( +      <View xmlns:android="http://schemas.android.com/apk/res/android" +            android:layout_width="match_parent" +            android:background="@color/green" +            android:text="hello" +            android:attr="\?hello" +            nonAaptAttr="1" +            nonAaptAttrRef="@id/id" +            class="hello" />)");    XmlReferenceLinker linker;    ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); -  xml::Element* view_el = xml::FindRootElement(doc.get()); +  xml::Element* view_el = doc->root.get();    ASSERT_THAT(view_el, NotNull());    xml::Attribute* xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "layout_width"); @@ -138,32 +138,32 @@ TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) {  }  TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreNotLinked) { -  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( -        <View xmlns:android="http://schemas.android.com/apk/res/android" -              android:colorAccent="@android:color/hidden" />)EOF"); +  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( +      <View xmlns:android="http://schemas.android.com/apk/res/android" +          android:colorAccent="@android:color/hidden" />)");    XmlReferenceLinker linker;    ASSERT_FALSE(linker.Consume(context_.get(), doc.get()));  }  TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix) { -  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( +  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"(      <View xmlns:android="http://schemas.android.com/apk/res/android" -          android:colorAccent="@*android:color/hidden" />)EOF"); +          android:colorAccent="@*android:color/hidden" />)");    XmlReferenceLinker linker;    ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));  }  TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) { -  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( -            <View xmlns:support="http://schemas.android.com/apk/res/com.android.support" -                  support:colorAccent="#ff0000" />)EOF"); +  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( +      <View xmlns:support="http://schemas.android.com/apk/res/com.android.support" +          support:colorAccent="#ff0000" />)");    XmlReferenceLinker linker;    ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); -  xml::Element* view_el = xml::FindRootElement(doc.get()); +  xml::Element* view_el = doc->root.get();    ASSERT_THAT(view_el, NotNull());    xml::Attribute* xml_attr = @@ -175,14 +175,14 @@ TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) {  }  TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) { -  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( -            <View xmlns:app="http://schemas.android.com/apk/res-auto" -                  app:colorAccent="@app:color/red" />)EOF"); +  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( +      <View xmlns:app="http://schemas.android.com/apk/res-auto" +          app:colorAccent="@app:color/red" />)");    XmlReferenceLinker linker;    ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); -  xml::Element* view_el = xml::FindRootElement(doc.get()); +  xml::Element* view_el = doc->root.get();    ASSERT_THAT(view_el, NotNull());    xml::Attribute* xml_attr = view_el->FindAttribute(xml::kSchemaAuto, "colorAccent"); @@ -196,17 +196,15 @@ TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) {  }  TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) { -  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( -            <View xmlns:app="http://schemas.android.com/apk/res/android" -                  app:attr="@app:id/id"> -              <View xmlns:app="http://schemas.android.com/apk/res/com.app.test" -                    app:attr="@app:id/id"/> -            </View>)EOF"); +  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( +      <View xmlns:app="http://schemas.android.com/apk/res/android" app:attr="@app:id/id"> +        <View xmlns:app="http://schemas.android.com/apk/res/com.app.test" app:attr="@app:id/id"/> +      </View>)");    XmlReferenceLinker linker;    ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); -  xml::Element* view_el = xml::FindRootElement(doc.get()); +  xml::Element* view_el = doc->root.get();    ASSERT_THAT(view_el, NotNull());    // All attributes and references in this element should be referring to @@ -235,14 +233,14 @@ TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) {  }  TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) { -  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( -            <View xmlns:android="http://schemas.android.com/apk/res/com.app.test" -                  android:attr="@id/id"/>)EOF"); +  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( +      <View xmlns:android="http://schemas.android.com/apk/res/com.app.test" +          android:attr="@id/id"/>)");    XmlReferenceLinker linker;    ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); -  xml::Element* view_el = xml::FindRootElement(doc.get()); +  xml::Element* view_el = doc->root.get();    ASSERT_THAT(view_el, NotNull());    // All attributes and references in this element should be referring to diff --git a/tools/aapt2/proto/ProtoHelpers.cpp b/tools/aapt2/proto/ProtoHelpers.cpp index 6b21364b5eb2..aa99c982f6ae 100644 --- a/tools/aapt2/proto/ProtoHelpers.cpp +++ b/tools/aapt2/proto/ProtoHelpers.cpp @@ -36,7 +36,7 @@ void SerializeSourceToPb(const Source& source, StringPool* src_pool, pb::Source*    StringPool::Ref ref = src_pool->MakeRef(source.path);    out_pb_source->set_path_idx(static_cast<uint32_t>(ref.index()));    if (source.line) { -    out_pb_source->set_line_no(static_cast<uint32_t>(source.line.value())); +    out_pb_source->mutable_position()->set_line_number(static_cast<uint32_t>(source.line.value()));    }  } @@ -46,29 +46,28 @@ void DeserializeSourceFromPb(const pb::Source& pb_source, const android::ResStri      out_source->path = util::GetString(src_pool, pb_source.path_idx());    } -  if (pb_source.has_line_no()) { -    out_source->line = static_cast<size_t>(pb_source.line_no()); +  if (pb_source.has_position()) { +    out_source->line = static_cast<size_t>(pb_source.position().line_number());    }  }  pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state) {    switch (state) {      case SymbolState::kPrivate: -      return pb::SymbolStatus_Visibility_Private; +      return pb::SymbolStatus_Visibility_PRIVATE;      case SymbolState::kPublic: -      return pb::SymbolStatus_Visibility_Public; +      return pb::SymbolStatus_Visibility_PUBLIC;      default:        break;    } -  return pb::SymbolStatus_Visibility_Unknown; +  return pb::SymbolStatus_Visibility_UNKNOWN;  } -SymbolState DeserializeVisibilityFromPb( -    pb::SymbolStatus_Visibility pb_visibility) { +SymbolState DeserializeVisibilityFromPb(pb::SymbolStatus_Visibility pb_visibility) {    switch (pb_visibility) { -    case pb::SymbolStatus_Visibility_Private: +    case pb::SymbolStatus_Visibility_PRIVATE:        return SymbolState::kPrivate; -    case pb::SymbolStatus_Visibility_Public: +    case pb::SymbolStatus_Visibility_PUBLIC:        return SymbolState::kPublic;      default:        break; @@ -102,20 +101,20 @@ bool DeserializeConfigDescriptionFromPb(const pb::ConfigDescription& pb_config,  pb::Reference_Type SerializeReferenceTypeToPb(Reference::Type type) {    switch (type) {      case Reference::Type::kResource: -      return pb::Reference_Type_Ref; +      return pb::Reference_Type_REFERENCE;      case Reference::Type::kAttribute: -      return pb::Reference_Type_Attr; +      return pb::Reference_Type_ATTRIBUTE;      default:        break;    } -  return pb::Reference_Type_Ref; +  return pb::Reference_Type_REFERENCE;  }  Reference::Type DeserializeReferenceTypeFromPb(pb::Reference_Type pb_type) {    switch (pb_type) { -    case pb::Reference_Type_Ref: +    case pb::Reference_Type_REFERENCE:        return Reference::Type::kResource; -    case pb::Reference_Type_Attr: +    case pb::Reference_Type_ATTRIBUTE:        return Reference::Type::kAttribute;      default:        break; @@ -126,32 +125,32 @@ Reference::Type DeserializeReferenceTypeFromPb(pb::Reference_Type pb_type) {  pb::Plural_Arity SerializePluralEnumToPb(size_t plural_idx) {    switch (plural_idx) {      case Plural::Zero: -      return pb::Plural_Arity_Zero; +      return pb::Plural_Arity_ZERO;      case Plural::One: -      return pb::Plural_Arity_One; +      return pb::Plural_Arity_ONE;      case Plural::Two: -      return pb::Plural_Arity_Two; +      return pb::Plural_Arity_TWO;      case Plural::Few: -      return pb::Plural_Arity_Few; +      return pb::Plural_Arity_FEW;      case Plural::Many: -      return pb::Plural_Arity_Many; +      return pb::Plural_Arity_MANY;      default:        break;    } -  return pb::Plural_Arity_Other; +  return pb::Plural_Arity_OTHER;  }  size_t DeserializePluralEnumFromPb(pb::Plural_Arity arity) {    switch (arity) { -    case pb::Plural_Arity_Zero: +    case pb::Plural_Arity_ZERO:        return Plural::Zero; -    case pb::Plural_Arity_One: +    case pb::Plural_Arity_ONE:        return Plural::One; -    case pb::Plural_Arity_Two: +    case pb::Plural_Arity_TWO:        return Plural::Two; -    case pb::Plural_Arity_Few: +    case pb::Plural_Arity_FEW:        return Plural::Few; -    case pb::Plural_Arity_Many: +    case pb::Plural_Arity_MANY:        return Plural::Many;      default:        break; diff --git a/tools/aapt2/proto/ProtoHelpers.h b/tools/aapt2/proto/ProtoHelpers.h index 344e9477ea71..2f268f44752c 100644 --- a/tools/aapt2/proto/ProtoHelpers.h +++ b/tools/aapt2/proto/ProtoHelpers.h @@ -23,27 +23,23 @@  #include "ResourceTable.h"  #include "Source.h"  #include "StringPool.h" -#include "Format.pb.h" +#include "Resources.pb.h" +#include "ResourcesInternal.pb.h"  namespace aapt { -void SerializeStringPoolToPb(const StringPool& pool, -                             pb::StringPool* out_pb_pool); +void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool); -void SerializeSourceToPb(const Source& source, StringPool* src_pool, -                         pb::Source* out_pb_source); +void SerializeSourceToPb(const Source& source, StringPool* src_pool, pb::Source* out_pb_source); -void DeserializeSourceFromPb(const pb::Source& pb_source, -                             const android::ResStringPool& src_pool, +void DeserializeSourceFromPb(const pb::Source& pb_source, const android::ResStringPool& src_pool,                               Source* out_source);  pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state); -SymbolState DeserializeVisibilityFromPb( -    pb::SymbolStatus_Visibility pb_visibility); +SymbolState DeserializeVisibilityFromPb(pb::SymbolStatus_Visibility pb_visibility); -void SerializeConfig(const ConfigDescription& config, -                     pb::ConfigDescription* out_pb_config); +void SerializeConfig(const ConfigDescription& config, pb::ConfigDescription* out_pb_config);  bool DeserializeConfigDescriptionFromPb(const pb::ConfigDescription& pb_config,                                          ConfigDescription* out_config); diff --git a/tools/aapt2/proto/ProtoSerialize.h b/tools/aapt2/proto/ProtoSerialize.h index 39c50038d599..8c46642e9090 100644 --- a/tools/aapt2/proto/ProtoSerialize.h +++ b/tools/aapt2/proto/ProtoSerialize.h @@ -30,11 +30,10 @@ namespace aapt {  class CompiledFileOutputStream {   public: -  explicit CompiledFileOutputStream( -      google::protobuf::io::ZeroCopyOutputStream* out); +  explicit CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out);    void WriteLittleEndian32(uint32_t value); -  void WriteCompiledFile(const pb::CompiledFile* compiledFile); +  void WriteCompiledFile(const pb::internal::CompiledFile* compiledFile);    void WriteData(const BigBuffer* buffer);    void WriteData(const void* data, size_t len);    bool HadError(); @@ -52,7 +51,7 @@ class CompiledFileInputStream {    explicit CompiledFileInputStream(const void* data, size_t size);    bool ReadLittleEndian32(uint32_t* outVal); -  bool ReadCompiledFile(pb::CompiledFile* outVal); +  bool ReadCompiledFile(pb::internal::CompiledFile* outVal);    bool ReadDataMetaData(uint64_t* outOffset, uint64_t* outLen);   private: @@ -64,13 +63,12 @@ class CompiledFileInputStream {  };  std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table); -std::unique_ptr<ResourceTable> DeserializeTableFromPb( -    const pb::ResourceTable& pbTable, const Source& source, IDiagnostics* diag); +std::unique_ptr<ResourceTable> DeserializeTableFromPb(const pb::ResourceTable& pbTable, +                                                      const Source& source, IDiagnostics* diag); -std::unique_ptr<pb::CompiledFile> SerializeCompiledFileToPb( -    const ResourceFile& file); +std::unique_ptr<pb::internal::CompiledFile> SerializeCompiledFileToPb(const ResourceFile& file);  std::unique_ptr<ResourceFile> DeserializeCompiledFileFromPb( -    const pb::CompiledFile& pbFile, const Source& source, IDiagnostics* diag); +    const pb::internal::CompiledFile& pbFile, const Source& source, IDiagnostics* diag);  }  // namespace aapt diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp index 37d5ed0cf59d..b9d5878f2f71 100644 --- a/tools/aapt2/proto/TableProtoDeserializer.cpp +++ b/tools/aapt2/proto/TableProtoDeserializer.cpp @@ -55,66 +55,61 @@ class ReferenceIdToNameVisitor : public ValueVisitor {  class PackagePbDeserializer {   public: -  PackagePbDeserializer(const android::ResStringPool* valuePool, -                        const android::ResStringPool* sourcePool, -                        const android::ResStringPool* symbolPool, -                        const Source& source, IDiagnostics* diag) -      : value_pool_(valuePool), -        source_pool_(sourcePool), -        symbol_pool_(symbolPool), -        source_(source), -        diag_(diag) {} +  PackagePbDeserializer(const android::ResStringPool* sourcePool, const Source& source, +                        IDiagnostics* diag) +      : source_pool_(sourcePool), source_(source), diag_(diag) { +  }   public: -  bool DeserializeFromPb(const pb::Package& pbPackage, ResourceTable* table) { +  bool DeserializeFromPb(const pb::Package& pb_package, ResourceTable* table) {      Maybe<uint8_t> id; -    if (pbPackage.has_package_id()) { -      id = static_cast<uint8_t>(pbPackage.package_id()); +    if (pb_package.has_package_id()) { +      id = static_cast<uint8_t>(pb_package.package_id());      } -    std::map<ResourceId, ResourceNameRef> idIndex; +    std::map<ResourceId, ResourceNameRef> id_index; -    ResourceTablePackage* pkg = table->CreatePackage(pbPackage.package_name(), id); -    for (const pb::Type& pbType : pbPackage.types()) { -      const ResourceType* resType = ParseResourceType(pbType.name()); -      if (!resType) { -        diag_->Error(DiagMessage(source_) << "unknown type '" << pbType.name() << "'"); +    ResourceTablePackage* pkg = table->CreatePackage(pb_package.package_name(), id); +    for (const pb::Type& pb_type : pb_package.type()) { +      const ResourceType* res_type = ParseResourceType(pb_type.name()); +      if (res_type == nullptr) { +        diag_->Error(DiagMessage(source_) << "unknown type '" << pb_type.name() << "'");          return {};        } -      ResourceTableType* type = pkg->FindOrCreateType(*resType); +      ResourceTableType* type = pkg->FindOrCreateType(*res_type); -      for (const pb::Entry& pbEntry : pbType.entries()) { -        ResourceEntry* entry = type->FindOrCreateEntry(pbEntry.name()); +      for (const pb::Entry& pb_entry : pb_type.entry()) { +        ResourceEntry* entry = type->FindOrCreateEntry(pb_entry.name()); -        // Deserialize the symbol status (public/private with source and -        // comments). -        if (pbEntry.has_symbol_status()) { -          const pb::SymbolStatus& pbStatus = pbEntry.symbol_status(); -          if (pbStatus.has_source()) { -            DeserializeSourceFromPb(pbStatus.source(), *source_pool_, &entry->symbol_status.source); +        // Deserialize the symbol status (public/private with source and comments). +        if (pb_entry.has_symbol_status()) { +          const pb::SymbolStatus& pb_status = pb_entry.symbol_status(); +          if (pb_status.has_source()) { +            DeserializeSourceFromPb(pb_status.source(), *source_pool_, +                                    &entry->symbol_status.source);            } -          if (pbStatus.has_comment()) { -            entry->symbol_status.comment = pbStatus.comment(); +          if (pb_status.has_comment()) { +            entry->symbol_status.comment = pb_status.comment();            } -          entry->symbol_status.allow_new = pbStatus.allow_new(); +          entry->symbol_status.allow_new = pb_status.allow_new(); -          SymbolState visibility = DeserializeVisibilityFromPb(pbStatus.visibility()); +          SymbolState visibility = DeserializeVisibilityFromPb(pb_status.visibility());            entry->symbol_status.state = visibility;            if (visibility == SymbolState::kPublic) {              // This is a public symbol, we must encode the ID now if there is one. -            if (pbEntry.has_id()) { -              entry->id = static_cast<uint16_t>(pbEntry.id()); +            if (pb_entry.has_id()) { +              entry->id = static_cast<uint16_t>(pb_entry.id());              }              if (type->symbol_status.state != SymbolState::kPublic) {                // If the type has not been made public, do so now.                type->symbol_status.state = SymbolState::kPublic; -              if (pbType.has_id()) { -                type->id = static_cast<uint8_t>(pbType.id()); +              if (pb_type.has_id()) { +                type->id = static_cast<uint8_t>(pb_type.id());                }              }            } else if (visibility == SymbolState::kPrivate) { @@ -124,45 +119,44 @@ class PackagePbDeserializer {            }          } -        ResourceId resId(pbPackage.package_id(), pbType.id(), pbEntry.id()); -        if (resId.is_valid()) { -          idIndex[resId] = ResourceNameRef(pkg->name, type->type, entry->name); +        ResourceId resid(pb_package.package_id(), pb_type.id(), pb_entry.id()); +        if (resid.is_valid()) { +          id_index[resid] = ResourceNameRef(pkg->name, type->type, entry->name);          } -        for (const pb::ConfigValue& pbConfigValue : pbEntry.config_values()) { -          const pb::ConfigDescription& pbConfig = pbConfigValue.config(); +        for (const pb::ConfigValue& pb_config_value : pb_entry.config_value()) { +          const pb::ConfigDescription& pb_config = pb_config_value.config();            ConfigDescription config; -          if (!DeserializeConfigDescriptionFromPb(pbConfig, &config)) { +          if (!DeserializeConfigDescriptionFromPb(pb_config, &config)) {              diag_->Error(DiagMessage(source_) << "invalid configuration");              return {};            } -          ResourceConfigValue* configValue = entry->FindOrCreateValue(config, pbConfig.product()); -          if (configValue->value) { +          ResourceConfigValue* config_value = entry->FindOrCreateValue(config, pb_config.product()); +          if (config_value->value) {              // Duplicate config.              diag_->Error(DiagMessage(source_) << "duplicate configuration");              return {};            } -          configValue->value = -              DeserializeValueFromPb(pbConfigValue.value(), config, &table->string_pool); -          if (!configValue->value) { +          config_value->value = +              DeserializeValueFromPb(pb_config_value.value(), config, &table->string_pool); +          if (!config_value->value) {              return {};            }          }        }      } -    ReferenceIdToNameVisitor visitor(&idIndex); +    ReferenceIdToNameVisitor visitor(&id_index);      VisitAllValuesInPackage(pkg, &visitor);      return true;    }   private:    std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item, -                                              const ConfigDescription& config, -                                              StringPool* pool) { +                                              const ConfigDescription& config, StringPool* pool) {      if (pb_item.has_ref()) {        const pb::Reference& pb_ref = pb_item.ref();        std::unique_ptr<Reference> ref = util::make_unique<Reference>(); @@ -173,45 +167,32 @@ class PackagePbDeserializer {      } else if (pb_item.has_prim()) {        const pb::Primitive& pb_prim = pb_item.prim(); -      android::Res_value prim = {}; -      prim.dataType = static_cast<uint8_t>(pb_prim.type()); -      prim.data = pb_prim.data(); -      return util::make_unique<BinaryPrimitive>(prim); +      return util::make_unique<BinaryPrimitive>(static_cast<uint8_t>(pb_prim.type()), +                                                pb_prim.data());      } else if (pb_item.has_id()) {        return util::make_unique<Id>();      } else if (pb_item.has_str()) { -      const uint32_t idx = pb_item.str().idx(); -      const std::string str = util::GetString(*value_pool_, idx); - -      const android::ResStringPool_span* spans = value_pool_->styleAt(idx); -      if (spans && spans->name.index != android::ResStringPool_span::END) { -        StyleString style_str = {str}; -        while (spans->name.index != android::ResStringPool_span::END) { -          style_str.spans.push_back( -              Span{util::GetString(*value_pool_, spans->name.index), -                   spans->firstChar, spans->lastChar}); -          spans++; -        } -        return util::make_unique<StyledString>(pool->MakeRef( -            style_str, StringPool::Context(StringPool::Context::kNormalPriority, config))); -      }        return util::make_unique<String>( -          pool->MakeRef(str, StringPool::Context(config))); +          pool->MakeRef(pb_item.str().value(), StringPool::Context(config)));      } else if (pb_item.has_raw_str()) { -      const uint32_t idx = pb_item.raw_str().idx(); -      const std::string str = util::GetString(*value_pool_, idx);        return util::make_unique<RawString>( -          pool->MakeRef(str, StringPool::Context(config))); +          pool->MakeRef(pb_item.raw_str().value(), StringPool::Context(config))); + +    } else if (pb_item.has_styled_str()) { +      const pb::StyledString& pb_str = pb_item.styled_str(); +      StyleString style_str{pb_str.value()}; +      for (const pb::StyledString::Span& pb_span : pb_str.span()) { +        style_str.spans.push_back(Span{pb_span.tag(), pb_span.first_char(), pb_span.last_char()}); +      } +      return util::make_unique<StyledString>(pool->MakeRef( +          style_str, StringPool::Context(StringPool::Context::kNormalPriority, config)));      } else if (pb_item.has_file()) { -      const uint32_t idx = pb_item.file().path_idx(); -      const std::string str = util::GetString(*value_pool_, idx);        return util::make_unique<FileReference>(pool->MakeRef( -          str, -          StringPool::Context(StringPool::Context::kHighPriority, config))); +          pb_item.file().path(), StringPool::Context(StringPool::Context::kHighPriority, config)));      } else {        diag_->Error(DiagMessage(source_) << "unknown item"); @@ -222,8 +203,6 @@ class PackagePbDeserializer {    std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,                                                  const ConfigDescription& config,                                                  StringPool* pool) { -    const bool is_weak = pb_value.has_weak() ? pb_value.weak() : false; -      std::unique_ptr<Value> value;      if (pb_value.has_item()) {        value = DeserializeItemFromPb(pb_value.item(), config, pool); @@ -235,11 +214,11 @@ class PackagePbDeserializer {        const pb::CompoundValue& pb_compound_value = pb_value.compound_value();        if (pb_compound_value.has_attr()) {          const pb::Attribute& pb_attr = pb_compound_value.attr(); -        std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(is_weak); +        std::unique_ptr<Attribute> attr = util::make_unique<Attribute>();          attr->type_mask = pb_attr.format_flags();          attr->min_int = pb_attr.min_int();          attr->max_int = pb_attr.max_int(); -        for (const pb::Attribute_Symbol& pb_symbol : pb_attr.symbols()) { +        for (const pb::Attribute_Symbol& pb_symbol : pb_attr.symbol()) {            Attribute::Symbol symbol;            DeserializeItemCommon(pb_symbol, &symbol.symbol);            if (!DeserializeReferenceFromPb(pb_symbol.name(), &symbol.symbol)) { @@ -255,20 +234,18 @@ class PackagePbDeserializer {          std::unique_ptr<Style> style = util::make_unique<Style>();          if (pb_style.has_parent()) {            style->parent = Reference(); -          if (!DeserializeReferenceFromPb(pb_style.parent(), -                                          &style->parent.value())) { +          if (!DeserializeReferenceFromPb(pb_style.parent(), &style->parent.value())) {              return {};            }            if (pb_style.has_parent_source()) {              Source parent_source; -            DeserializeSourceFromPb(pb_style.parent_source(), *source_pool_, -                                    &parent_source); +            DeserializeSourceFromPb(pb_style.parent_source(), *source_pool_, &parent_source);              style->parent.value().SetSource(std::move(parent_source));            }          } -        for (const pb::Style_Entry& pb_entry : pb_style.entries()) { +        for (const pb::Style_Entry& pb_entry : pb_style.entry()) {            Style::Entry entry;            DeserializeItemCommon(pb_entry, &entry.key);            if (!DeserializeReferenceFromPb(pb_entry.key(), &entry.key)) { @@ -288,7 +265,7 @@ class PackagePbDeserializer {        } else if (pb_compound_value.has_styleable()) {          const pb::Styleable& pb_styleable = pb_compound_value.styleable();          std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); -        for (const pb::Styleable_Entry& pb_entry : pb_styleable.entries()) { +        for (const pb::Styleable_Entry& pb_entry : pb_styleable.entry()) {            Reference attr_ref;            DeserializeItemCommon(pb_entry, &attr_ref);            DeserializeReferenceFromPb(pb_entry.attr(), &attr_ref); @@ -299,25 +276,23 @@ class PackagePbDeserializer {        } else if (pb_compound_value.has_array()) {          const pb::Array& pb_array = pb_compound_value.array();          std::unique_ptr<Array> array = util::make_unique<Array>(); -        for (const pb::Array_Entry& pb_entry : pb_array.entries()) { -          std::unique_ptr<Item> item = -              DeserializeItemFromPb(pb_entry.item(), config, pool); +        for (const pb::Array_Element& pb_entry : pb_array.element()) { +          std::unique_ptr<Item> item = DeserializeItemFromPb(pb_entry.item(), config, pool);            if (!item) {              return {};            }            DeserializeItemCommon(pb_entry, item.get()); -          array->items.push_back(std::move(item)); +          array->elements.push_back(std::move(item));          }          value = std::move(array);        } else if (pb_compound_value.has_plural()) {          const pb::Plural& pb_plural = pb_compound_value.plural();          std::unique_ptr<Plural> plural = util::make_unique<Plural>(); -        for (const pb::Plural_Entry& pb_entry : pb_plural.entries()) { +        for (const pb::Plural_Entry& pb_entry : pb_plural.entry()) {            size_t pluralIdx = DeserializePluralEnumFromPb(pb_entry.arity()); -          plural->values[pluralIdx] = -              DeserializeItemFromPb(pb_entry.item(), config, pool); +          plural->values[pluralIdx] = DeserializeItemFromPb(pb_entry.item(), config, pool);            if (!plural->values[pluralIdx]) {              return {};            } @@ -337,7 +312,7 @@ class PackagePbDeserializer {      CHECK(value) << "forgot to set value"; -    value->SetWeak(is_weak); +    value->SetWeak(pb_value.weak());      DeserializeItemCommon(pb_value, value.get());      return value;    } @@ -350,11 +325,10 @@ class PackagePbDeserializer {        out_ref->id = ResourceId(pb_ref.id());      } -    if (pb_ref.has_symbol_idx()) { -      const std::string str_symbol = util::GetString(*symbol_pool_, pb_ref.symbol_idx()); +    if (pb_ref.has_name()) {        ResourceNameRef name_ref; -      if (!ResourceUtils::ParseResourceName(str_symbol, &name_ref, nullptr)) { -        diag_->Error(DiagMessage(source_) << "invalid reference name '" << str_symbol << "'"); +      if (!ResourceUtils::ParseResourceName(pb_ref.name(), &name_ref, nullptr)) { +        diag_->Error(DiagMessage(source_) << "invalid reference name '" << pb_ref.name() << "'");          return false;        } @@ -377,61 +351,33 @@ class PackagePbDeserializer {    }   private: -  const android::ResStringPool* value_pool_;    const android::ResStringPool* source_pool_; -  const android::ResStringPool* symbol_pool_;    const Source source_;    IDiagnostics* diag_;  };  }  // namespace -std::unique_ptr<ResourceTable> DeserializeTableFromPb( -    const pb::ResourceTable& pb_table, const Source& source, -    IDiagnostics* diag) { -  // We import the android namespace because on Windows NO_ERROR is a macro, not -  // an enum, which +std::unique_ptr<ResourceTable> DeserializeTableFromPb(const pb::ResourceTable& pb_table, +                                                      const Source& source, IDiagnostics* diag) { +  // We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which    // causes errors when qualifying it with android::    using namespace android;    std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); -  if (!pb_table.has_string_pool()) { -    diag->Error(DiagMessage(source) << "no string pool found"); -    return {}; -  } - -  ResStringPool value_pool; -  status_t result = value_pool.setTo(pb_table.string_pool().data().data(), -                                     pb_table.string_pool().data().size()); -  if (result != NO_ERROR) { -    diag->Error(DiagMessage(source) << "invalid string pool"); -    return {}; -  } -    ResStringPool source_pool;    if (pb_table.has_source_pool()) { -    result = source_pool.setTo(pb_table.source_pool().data().data(), -                               pb_table.source_pool().data().size()); +    status_t result = source_pool.setTo(pb_table.source_pool().data().data(), +                                        pb_table.source_pool().data().size());      if (result != NO_ERROR) {        diag->Error(DiagMessage(source) << "invalid source pool");        return {};      }    } -  ResStringPool symbol_pool; -  if (pb_table.has_symbol_pool()) { -    result = symbol_pool.setTo(pb_table.symbol_pool().data().data(), -                               pb_table.symbol_pool().data().size()); -    if (result != NO_ERROR) { -      diag->Error(DiagMessage(source) << "invalid symbol pool"); -      return {}; -    } -  } - -  PackagePbDeserializer package_pb_deserializer(&value_pool, &source_pool, -                                                &symbol_pool, source, diag); -  for (const pb::Package& pb_package : pb_table.packages()) { +  PackagePbDeserializer package_pb_deserializer(&source_pool, source, diag); +  for (const pb::Package& pb_package : pb_table.package()) {      if (!package_pb_deserializer.DeserializeFromPb(pb_package, table.get())) {        return {};      } @@ -440,7 +386,7 @@ std::unique_ptr<ResourceTable> DeserializeTableFromPb(  }  std::unique_ptr<ResourceFile> DeserializeCompiledFileFromPb( -    const pb::CompiledFile& pb_file, const Source& source, IDiagnostics* diag) { +    const pb::internal::CompiledFile& pb_file, const Source& source, IDiagnostics* diag) {    std::unique_ptr<ResourceFile> file = util::make_unique<ResourceFile>();    ResourceNameRef name_ref; @@ -456,19 +402,20 @@ std::unique_ptr<ResourceFile> DeserializeCompiledFileFromPb(    file->source.path = pb_file.source_path();    DeserializeConfigDescriptionFromPb(pb_file.config(), &file->config); -  for (const pb::CompiledFile_Symbol& pb_symbol : pb_file.exported_symbols()) { -    // Need to create an lvalue here so that nameRef can point to something -    // real. -    if (!ResourceUtils::ParseResourceName(pb_symbol.resource_name(), -                                          &name_ref)) { +  for (const pb::internal::CompiledFile_Symbol& pb_symbol : pb_file.exported_symbol()) { +    // Need to create an lvalue here so that nameRef can point to something real. +    if (!ResourceUtils::ParseResourceName(pb_symbol.resource_name(), &name_ref)) {        diag->Error(DiagMessage(source)                    << "invalid resource name for exported symbol in "                       "compiled file header: "                    << pb_file.resource_name());        return {};      } -    file->exported_symbols.push_back( -        SourcedResourceName{name_ref.ToResourceName(), pb_symbol.line_no()}); +    size_t line = 0u; +    if (pb_symbol.has_source()) { +      line = pb_symbol.source().line_number(); +    } +    file->exported_symbols.push_back(SourcedResourceName{name_ref.ToResourceName(), line});    }    return file;  } diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp index 730442c62836..a08df71eae1e 100644 --- a/tools/aapt2/proto/TableProtoSerializer.cpp +++ b/tools/aapt2/proto/TableProtoSerializer.cpp @@ -24,9 +24,9 @@  #include "android-base/logging.h" -using google::protobuf::io::CodedOutputStream; -using google::protobuf::io::CodedInputStream; -using google::protobuf::io::ZeroCopyOutputStream; +using ::google::protobuf::io::CodedInputStream; +using ::google::protobuf::io::CodedOutputStream; +using ::google::protobuf::io::ZeroCopyOutputStream;  namespace aapt { @@ -36,46 +36,46 @@ class PbSerializerVisitor : public RawValueVisitor {   public:    using RawValueVisitor::Visit; -  /** -   * Constructor to use when expecting to serialize any value. -   */ -  PbSerializerVisitor(StringPool* source_pool, StringPool* symbol_pool, -                      pb::Value* out_pb_value) -      : source_pool_(source_pool), -        symbol_pool_(symbol_pool), -        out_pb_value_(out_pb_value), -        out_pb_item_(nullptr) {} - -  /** -   * Constructor to use when expecting to serialize an Item. -   */ -  PbSerializerVisitor(StringPool* sourcePool, StringPool* symbolPool, -                      pb::Item* outPbItem) -      : source_pool_(sourcePool), -        symbol_pool_(symbolPool), -        out_pb_value_(nullptr), -        out_pb_item_(outPbItem) {} +  // Constructor to use when expecting to serialize any value. +  PbSerializerVisitor(StringPool* source_pool, pb::Value* out_pb_value) +      : source_pool_(source_pool), out_pb_value_(out_pb_value), out_pb_item_(nullptr) { +  } + +  // Constructor to use when expecting to serialize an Item. +  PbSerializerVisitor(StringPool* sourcePool, pb::Item* outPbItem) +      : source_pool_(sourcePool), out_pb_value_(nullptr), out_pb_item_(outPbItem) { +  }    void Visit(Reference* ref) override {      SerializeReferenceToPb(*ref, pb_item()->mutable_ref());    }    void Visit(String* str) override { -    pb_item()->mutable_str()->set_idx(str->value.index()); +    pb_item()->mutable_str()->set_value(*str->value); +  } + +  void Visit(RawString* str) override { +    pb_item()->mutable_raw_str()->set_value(*str->value);    }    void Visit(StyledString* str) override { -    pb_item()->mutable_str()->set_idx(str->value.index()); +    pb::StyledString* pb_str = pb_item()->mutable_styled_str(); +    pb_str->set_value(str->value->value); + +    for (const StringPool::Span& span : str->value->spans) { +      pb::StyledString::Span* pb_span = pb_str->add_span(); +      pb_span->set_tag(*span.name); +      pb_span->set_first_char(span.first_char); +      pb_span->set_last_char(span.last_char); +    }    }    void Visit(FileReference* file) override { -    pb_item()->mutable_file()->set_path_idx(file->path.index()); +    pb_item()->mutable_file()->set_path(*file->path);    } -  void Visit(Id* id) override { pb_item()->mutable_id(); } - -  void Visit(RawString* raw_str) override { -    pb_item()->mutable_raw_str()->set_idx(raw_str->value.index()); +  void Visit(Id* /*id*/) override { +    pb_item()->mutable_id();    }    void Visit(BinaryPrimitive* prim) override { @@ -98,7 +98,7 @@ class PbSerializerVisitor : public RawValueVisitor {      pb_attr->set_max_int(attr->max_int);      for (auto& symbol : attr->symbols) { -      pb::Attribute_Symbol* pb_symbol = pb_attr->add_symbols(); +      pb::Attribute_Symbol* pb_symbol = pb_attr->add_symbol();        SerializeItemCommonToPb(symbol.symbol, pb_symbol);        SerializeReferenceToPb(symbol.symbol, pb_symbol->mutable_name());        pb_symbol->set_value(symbol.value); @@ -114,12 +114,12 @@ class PbSerializerVisitor : public RawValueVisitor {      }      for (Style::Entry& entry : style->entries) { -      pb::Style_Entry* pb_entry = pb_style->add_entries(); +      pb::Style_Entry* pb_entry = pb_style->add_entry();        SerializeReferenceToPb(entry.key, pb_entry->mutable_key());        pb::Item* pb_item = pb_entry->mutable_item();        SerializeItemCommonToPb(entry.key, pb_entry); -      PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_, pb_item); +      PbSerializerVisitor sub_visitor(source_pool_, pb_item);        entry.value->Accept(&sub_visitor);      }    } @@ -127,7 +127,7 @@ class PbSerializerVisitor : public RawValueVisitor {    void Visit(Styleable* styleable) override {      pb::Styleable* pb_styleable = pb_compound_value()->mutable_styleable();      for (Reference& entry : styleable->entries) { -      pb::Styleable_Entry* pb_entry = pb_styleable->add_entries(); +      pb::Styleable_Entry* pb_entry = pb_styleable->add_entry();        SerializeItemCommonToPb(entry, pb_entry);        SerializeReferenceToPb(entry, pb_entry->mutable_attr());      } @@ -135,11 +135,10 @@ class PbSerializerVisitor : public RawValueVisitor {    void Visit(Array* array) override {      pb::Array* pb_array = pb_compound_value()->mutable_array(); -    for (auto& value : array->items) { -      pb::Array_Entry* pb_entry = pb_array->add_entries(); -      SerializeItemCommonToPb(*value, pb_entry); -      PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_, -                                      pb_entry->mutable_item()); +    for (auto& value : array->elements) { +      pb::Array_Element* pb_element = pb_array->add_element(); +      SerializeItemCommonToPb(*value, pb_element); +      PbSerializerVisitor sub_visitor(source_pool_, pb_element->mutable_item());        value->Accept(&sub_visitor);      }    } @@ -153,11 +152,11 @@ class PbSerializerVisitor : public RawValueVisitor {          continue;        } -      pb::Plural_Entry* pb_entry = pb_plural->add_entries(); +      pb::Plural_Entry* pb_entry = pb_plural->add_entry();        pb_entry->set_arity(SerializePluralEnumToPb(i));        pb::Item* pb_element = pb_entry->mutable_item();        SerializeItemCommonToPb(*plural->values[i], pb_entry); -      PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_, pb_element); +      PbSerializerVisitor sub_visitor(source_pool_, pb_element);        plural->values[i]->Accept(&sub_visitor);      }    } @@ -179,8 +178,7 @@ class PbSerializerVisitor : public RawValueVisitor {    template <typename T>    void SerializeItemCommonToPb(const Item& item, T* pb_item) { -    SerializeSourceToPb(item.GetSource(), source_pool_, -                        pb_item->mutable_source()); +    SerializeSourceToPb(item.GetSource(), source_pool_, pb_item->mutable_source());      if (!item.GetComment().empty()) {        pb_item->set_comment(item.GetComment());      } @@ -192,8 +190,7 @@ class PbSerializerVisitor : public RawValueVisitor {      }      if (ref.name) { -      StringPool::Ref symbol_ref = symbol_pool_->MakeRef(ref.name.value().ToString()); -      pb_ref->set_symbol_idx(static_cast<uint32_t>(symbol_ref.index())); +      pb_ref->set_name(ref.name.value().ToString());      }      pb_ref->set_private_(ref.private_reference); @@ -201,7 +198,6 @@ class PbSerializerVisitor : public RawValueVisitor {    }    StringPool* source_pool_; -  StringPool* symbol_pool_;    pb::Value* out_pb_value_;    pb::Item* out_pb_item_;  }; @@ -220,26 +216,24 @@ std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) {    });    auto pb_table = util::make_unique<pb::ResourceTable>(); -  SerializeStringPoolToPb(table->string_pool, pb_table->mutable_string_pool()); - -  StringPool source_pool, symbol_pool; +  StringPool source_pool;    for (auto& package : table->packages) { -    pb::Package* pb_package = pb_table->add_packages(); +    pb::Package* pb_package = pb_table->add_package();      if (package->id) {        pb_package->set_package_id(package->id.value());      }      pb_package->set_package_name(package->name);      for (auto& type : package->types) { -      pb::Type* pb_type = pb_package->add_types(); +      pb::Type* pb_type = pb_package->add_type();        if (type->id) {          pb_type->set_id(type->id.value());        }        pb_type->set_name(ToString(type->type).to_string());        for (auto& entry : type->entries) { -        pb::Entry* pb_entry = pb_type->add_entries(); +        pb::Entry* pb_entry = pb_type->add_entry();          if (entry->id) {            pb_entry->set_id(entry->id.value());          } @@ -253,7 +247,7 @@ std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) {          pb_status->set_allow_new(entry->symbol_status.allow_new);          for (auto& config_value : entry->values) { -          pb::ConfigValue* pb_config_value = pb_entry->add_config_values(); +          pb::ConfigValue* pb_config_value = pb_entry->add_config_value();            SerializeConfig(config_value->config, pb_config_value->mutable_config());            if (!config_value->product.empty()) {              pb_config_value->mutable_config()->set_product(config_value->product); @@ -270,7 +264,7 @@ std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) {              pb_value->set_weak(true);            } -          PbSerializerVisitor visitor(&source_pool, &symbol_pool, pb_value); +          PbSerializerVisitor visitor(&source_pool, pb_value);            config_value->value->Accept(&visitor);          }        } @@ -278,27 +272,25 @@ std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) {    }    SerializeStringPoolToPb(source_pool, pb_table->mutable_source_pool()); -  SerializeStringPoolToPb(symbol_pool, pb_table->mutable_symbol_pool());    return pb_table;  } -std::unique_ptr<pb::CompiledFile> SerializeCompiledFileToPb( -    const ResourceFile& file) { -  auto pb_file = util::make_unique<pb::CompiledFile>(); +std::unique_ptr<pb::internal::CompiledFile> SerializeCompiledFileToPb(const ResourceFile& file) { +  auto pb_file = util::make_unique<pb::internal::CompiledFile>();    pb_file->set_resource_name(file.name.ToString());    pb_file->set_source_path(file.source.path);    SerializeConfig(file.config, pb_file->mutable_config());    for (const SourcedResourceName& exported : file.exported_symbols) { -    pb::CompiledFile_Symbol* pb_symbol = pb_file->add_exported_symbols(); +    pb::internal::CompiledFile_Symbol* pb_symbol = pb_file->add_exported_symbol();      pb_symbol->set_resource_name(exported.name.ToString()); -    pb_symbol->set_line_no(exported.line); +    pb_symbol->mutable_source()->set_line_number(exported.line);    }    return pb_file;  } -CompiledFileOutputStream::CompiledFileOutputStream(ZeroCopyOutputStream* out) -    : out_(out) {} +CompiledFileOutputStream::CompiledFileOutputStream(ZeroCopyOutputStream* out) : out_(out) { +}  void CompiledFileOutputStream::EnsureAlignedWrite() {    const int padding = out_.ByteCount() % 4; @@ -313,8 +305,7 @@ void CompiledFileOutputStream::WriteLittleEndian32(uint32_t val) {    out_.WriteLittleEndian32(val);  } -void CompiledFileOutputStream::WriteCompiledFile( -    const pb::CompiledFile* compiled_file) { +void CompiledFileOutputStream::WriteCompiledFile(const pb::internal::CompiledFile* compiled_file) {    EnsureAlignedWrite();    out_.WriteLittleEndian64(static_cast<uint64_t>(compiled_file->ByteSize()));    compiled_file->SerializeWithCachedSizes(&out_); @@ -334,7 +325,9 @@ void CompiledFileOutputStream::WriteData(const void* data, size_t len) {    out_.WriteRaw(data, len);  } -bool CompiledFileOutputStream::HadError() { return out_.HadError(); } +bool CompiledFileOutputStream::HadError() { +  return out_.HadError(); +}  CompiledFileInputStream::CompiledFileInputStream(const void* data, size_t size)      : in_(static_cast<const uint8_t*>(data), size) {} @@ -352,7 +345,7 @@ bool CompiledFileInputStream::ReadLittleEndian32(uint32_t* out_val) {    return in_.ReadLittleEndian32(out_val);  } -bool CompiledFileInputStream::ReadCompiledFile(pb::CompiledFile* out_val) { +bool CompiledFileInputStream::ReadCompiledFile(pb::internal::CompiledFile* out_val) {    EnsureAlignedRead();    google::protobuf::uint64 pb_size = 0u; @@ -379,8 +372,7 @@ bool CompiledFileInputStream::ReadCompiledFile(pb::CompiledFile* out_val) {    return true;  } -bool CompiledFileInputStream::ReadDataMetaData(uint64_t* out_offset, -                                               uint64_t* out_len) { +bool CompiledFileInputStream::ReadDataMetaData(uint64_t* out_offset, uint64_t* out_len) {    EnsureAlignedRead();    google::protobuf::uint64 pb_size = 0u; diff --git a/tools/aapt2/proto/TableProtoSerializer_test.cpp b/tools/aapt2/proto/TableProtoSerializer_test.cpp index 3ebb08eb791e..80608b3d9c05 100644 --- a/tools/aapt2/proto/TableProtoSerializer_test.cpp +++ b/tools/aapt2/proto/TableProtoSerializer_test.cpp @@ -20,7 +20,9 @@  #include "test/Test.h"  using ::google::protobuf::io::StringOutputStream; +using ::testing::Eq;  using ::testing::NotNull; +using ::testing::SizeIs;  namespace aapt { @@ -38,12 +40,12 @@ TEST(TableProtoSerializer, SerializeSinglePackage) {    Symbol public_symbol;    public_symbol.state = SymbolState::kPublic; -  ASSERT_TRUE(table->SetSymbolState( -      test::ParseNameOrDie("com.app.a:layout/main"), ResourceId(0x7f020000), -      public_symbol, context->GetDiagnostics())); +  ASSERT_TRUE(table->SetSymbolState(test::ParseNameOrDie("com.app.a:layout/main"), +                                    ResourceId(0x7f020000), public_symbol, +                                    context->GetDiagnostics()));    Id* id = test::GetValue<Id>(table.get(), "com.app.a:id/foo"); -  ASSERT_NE(nullptr, id); +  ASSERT_THAT(id, NotNull());    // Make a plural.    std::unique_ptr<Plural> plural = util::make_unique<Plural>(); @@ -52,6 +54,15 @@ TEST(TableProtoSerializer, SerializeSinglePackage) {                                   ConfigDescription{}, {}, std::move(plural),                                   context->GetDiagnostics())); +  // Make a styled string. +  StyleString style_string; +  style_string.str = "hello"; +  style_string.spans.push_back(Span{"b", 0u, 4u}); +  ASSERT_TRUE( +      table->AddResource(test::ParseNameOrDie("com.app.a:string/styled"), ConfigDescription{}, {}, +                         util::make_unique<StyledString>(table->string_pool.MakeRef(style_string)), +                         context->GetDiagnostics())); +    // Make a resource with different products.    ASSERT_TRUE(table->AddResource(        test::ParseNameOrDie("com.app.a:integer/one"), @@ -65,9 +76,8 @@ TEST(TableProtoSerializer, SerializeSinglePackage) {        context->GetDiagnostics()));    // Make a reference with both resource name and resource ID. -  // The reference should point to a resource outside of this table to test that -  // both -  // name and id get serialized. +  // The reference should point to a resource outside of this table to test that both name and id +  // get serialized.    Reference expected_ref;    expected_ref.name = test::ParseNameOrDie("android:layout/main");    expected_ref.id = ResourceId(0x01020000); @@ -85,36 +95,45 @@ TEST(TableProtoSerializer, SerializeSinglePackage) {    Id* new_id = test::GetValue<Id>(new_table.get(), "com.app.a:id/foo");    ASSERT_THAT(new_id, NotNull()); -  EXPECT_EQ(id->IsWeak(), new_id->IsWeak()); +  EXPECT_THAT(new_id->IsWeak(), Eq(id->IsWeak()));    Maybe<ResourceTable::SearchResult> result =        new_table->FindResource(test::ParseNameOrDie("com.app.a:layout/main"));    ASSERT_TRUE(result); -  EXPECT_EQ(SymbolState::kPublic, result.value().type->symbol_status.state); -  EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbol_status.state); + +  EXPECT_THAT(result.value().type->symbol_status.state, Eq(SymbolState::kPublic)); +  EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kPublic));    result = new_table->FindResource(test::ParseNameOrDie("com.app.a:bool/foo"));    ASSERT_TRUE(result); -  EXPECT_EQ(SymbolState::kUndefined, result.value().entry->symbol_status.state); +  EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kUndefined));    EXPECT_TRUE(result.value().entry->symbol_status.allow_new);    // Find the product-dependent values    BinaryPrimitive* prim = test::GetValueForConfigAndProduct<BinaryPrimitive>(        new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"), "");    ASSERT_THAT(prim, NotNull()); -  EXPECT_EQ(123u, prim->value.data); +  EXPECT_THAT(prim->value.data, Eq(123u));    prim = test::GetValueForConfigAndProduct<BinaryPrimitive>(        new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"), "tablet");    ASSERT_THAT(prim, NotNull()); -  EXPECT_EQ(321u, prim->value.data); +  EXPECT_THAT(prim->value.data, Eq(321u));    Reference* actual_ref = test::GetValue<Reference>(new_table.get(), "com.app.a:layout/abc");    ASSERT_THAT(actual_ref, NotNull());    ASSERT_TRUE(actual_ref->name);    ASSERT_TRUE(actual_ref->id); -  EXPECT_EQ(expected_ref.name.value(), actual_ref->name.value()); -  EXPECT_EQ(expected_ref.id.value(), actual_ref->id.value()); +  EXPECT_THAT(*actual_ref, Eq(expected_ref)); + +  StyledString* actual_styled_str = +      test::GetValue<StyledString>(new_table.get(), "com.app.a:string/styled"); +  ASSERT_THAT(actual_styled_str, NotNull()); +  EXPECT_THAT(actual_styled_str->value->value, Eq("hello")); +  ASSERT_THAT(actual_styled_str->value->spans, SizeIs(1u)); +  EXPECT_THAT(*actual_styled_str->value->spans[0].name, Eq("b")); +  EXPECT_THAT(actual_styled_str->value->spans[0].first_char, Eq(0u)); +  EXPECT_THAT(actual_styled_str->value->spans[0].last_char, Eq(4u));  }  TEST(TableProtoSerializer, SerializeFileHeader) { @@ -132,10 +151,10 @@ TEST(TableProtoSerializer, SerializeFileHeader) {    std::string output_str;    { -    std::unique_ptr<pb::CompiledFile> pb_file1 = SerializeCompiledFileToPb(f); +    std::unique_ptr<pb::internal::CompiledFile> pb_file1 = SerializeCompiledFileToPb(f);      f.name.entry = "__" + f.name.entry + "$0"; -    std::unique_ptr<pb::CompiledFile> pb_file2 = SerializeCompiledFileToPb(f); +    std::unique_ptr<pb::internal::CompiledFile> pb_file2 = SerializeCompiledFileToPb(f);      StringOutputStream out_stream(&output_str);      CompiledFileOutputStream out_file_stream(&out_stream); @@ -154,7 +173,7 @@ TEST(TableProtoSerializer, SerializeFileHeader) {    // Read the first compiled file. -  pb::CompiledFile new_pb_file; +  pb::internal::CompiledFile new_pb_file;    ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_file));    std::unique_ptr<ResourceFile> file = DeserializeCompiledFileFromPb( @@ -191,7 +210,7 @@ TEST(TableProtoSerializer, SerializeFileHeader) {  TEST(TableProtoSerializer, DeserializeCorruptHeaderSafely) {    ResourceFile f; -  std::unique_ptr<pb::CompiledFile> pb_file = SerializeCompiledFileToPb(f); +  std::unique_ptr<pb::internal::CompiledFile> pb_file = SerializeCompiledFileToPb(f);    const std::string expected_data = "1234"; @@ -213,7 +232,7 @@ TEST(TableProtoSerializer, DeserializeCorruptHeaderSafely) {    EXPECT_TRUE(in_file_stream.ReadLittleEndian32(&num_files));    EXPECT_EQ(1u, num_files); -  pb::CompiledFile new_pb_file; +  pb::internal::CompiledFile new_pb_file;    EXPECT_FALSE(in_file_stream.ReadCompiledFile(&new_pb_file));    uint64_t offset, len; diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md index c8d36177beb4..8368f9d16af8 100644 --- a/tools/aapt2/readme.md +++ b/tools/aapt2/readme.md @@ -1,5 +1,18 @@  # Android Asset Packaging Tool 2.0 (AAPT2) release notes +## Version 2.19 +- Added navigation resource type. +- Fixed issue with resource deduplication. (bug 64397629) +- Added a daemon mode for issuing commands. This is invoked with `aapt2 daemon`. +  Command line arguments are separated by newlines, with an empty line signalling the +  end of a command. Sending `EOF (Ctrl+D)` to the daemon will exit. +- Fixed an issue where multiple permissions defined in AndroidManifest.xml would generate +  conflicting definitions for the same Java constant in Manifest.java. Changed the implementation +  to match that of `aapt`, which will take the last definition as the sole definition. +  A warning is logged if such a scenario occurs. (bug 64472942) +- Made improvements to handling of paths on Windows. This should resolve a lot of issues with +  Unicode paths. (bug 62336414, 63830502) +  ## Version 2.18  ### `aapt2 ...`  - Fixed issue where enum values were interpreted as integers and range checked. (bug 62358540) diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp new file mode 100644 index 000000000000..8f9788e8a25d --- /dev/null +++ b/tools/aapt2/test/Builders.cpp @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test/Builders.h" + +#include "android-base/logging.h" +#include "androidfw/StringPiece.h" + +#include "io/StringInputStream.h" +#include "test/Common.h" +#include "util/Util.h" + +using ::aapt::io::StringInputStream; +using ::android::StringPiece; + +namespace aapt { +namespace test { + +ResourceTableBuilder& ResourceTableBuilder::SetPackageId(const StringPiece& package_name, +                                                         uint8_t id) { +  ResourceTablePackage* package = table_->CreatePackage(package_name, id); +  CHECK(package != nullptr); +  return *this; +} + +ResourceTableBuilder& ResourceTableBuilder::AddSimple(const StringPiece& name, +                                                      const ResourceId& id) { +  return AddValue(name, id, util::make_unique<Id>()); +} + +ResourceTableBuilder& ResourceTableBuilder::AddSimple(const StringPiece& name, +                                                      const ConfigDescription& config, +                                                      const ResourceId& id) { +  return AddValue(name, config, id, util::make_unique<Id>()); +} + +ResourceTableBuilder& ResourceTableBuilder::AddReference(const StringPiece& name, +                                                         const StringPiece& ref) { +  return AddReference(name, {}, ref); +} + +ResourceTableBuilder& ResourceTableBuilder::AddReference(const StringPiece& name, +                                                         const ResourceId& id, +                                                         const StringPiece& ref) { +  return AddValue(name, id, util::make_unique<Reference>(ParseNameOrDie(ref))); +} + +ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, +                                                      const StringPiece& str) { +  return AddString(name, {}, str); +} + +ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, const ResourceId& id, +                                                      const StringPiece& str) { +  return AddValue(name, id, util::make_unique<String>(table_->string_pool.MakeRef(str))); +} + +ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, const ResourceId& id, +                                                      const ConfigDescription& config, +                                                      const StringPiece& str) { +  return AddValue(name, config, id, util::make_unique<String>(table_->string_pool.MakeRef(str))); +} + +ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name, +                                                             const StringPiece& path) { +  return AddFileReference(name, {}, path); +} + +ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name, +                                                             const ResourceId& id, +                                                             const StringPiece& path) { +  return AddValue(name, id, util::make_unique<FileReference>(table_->string_pool.MakeRef(path))); +} + +ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name, +                                                             const StringPiece& path, +                                                             const ConfigDescription& config) { +  return AddValue(name, config, {}, +                  util::make_unique<FileReference>(table_->string_pool.MakeRef(path))); +} + +ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, +                                                     std::unique_ptr<Value> value) { +  return AddValue(name, {}, std::move(value)); +} + +ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, const ResourceId& id, +                                                     std::unique_ptr<Value> value) { +  return AddValue(name, {}, id, std::move(value)); +} + +ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, +                                                     const ConfigDescription& config, +                                                     const ResourceId& id, +                                                     std::unique_ptr<Value> value) { +  ResourceName res_name = ParseNameOrDie(name); +  CHECK(table_->AddResourceAllowMangled(res_name, id, config, {}, std::move(value), +                                        GetDiagnostics())); +  return *this; +} + +ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& name, +                                                           const ResourceId& id, SymbolState state, +                                                           bool allow_new) { +  ResourceName res_name = ParseNameOrDie(name); +  Symbol symbol; +  symbol.state = state; +  symbol.allow_new = allow_new; +  CHECK(table_->SetSymbolStateAllowMangled(res_name, id, symbol, GetDiagnostics())); +  return *this; +} + +StringPool* ResourceTableBuilder::string_pool() { +  return &table_->string_pool; +} + +std::unique_ptr<ResourceTable> ResourceTableBuilder::Build() { +  return std::move(table_); +} + +std::unique_ptr<Reference> BuildReference(const StringPiece& ref, const Maybe<ResourceId>& id) { +  std::unique_ptr<Reference> reference = util::make_unique<Reference>(ParseNameOrDie(ref)); +  reference->id = id; +  return reference; +} + +std::unique_ptr<BinaryPrimitive> BuildPrimitive(uint8_t type, uint32_t data) { +  android::Res_value value = {}; +  value.size = sizeof(value); +  value.dataType = type; +  value.data = data; +  return util::make_unique<BinaryPrimitive>(value); +} + +AttributeBuilder::AttributeBuilder(bool weak) : attr_(util::make_unique<Attribute>(weak)) { +  attr_->type_mask = android::ResTable_map::TYPE_ANY; +} + +AttributeBuilder& AttributeBuilder::SetTypeMask(uint32_t typeMask) { +  attr_->type_mask = typeMask; +  return *this; +} + +AttributeBuilder& AttributeBuilder::AddItem(const StringPiece& name, uint32_t value) { +  attr_->symbols.push_back( +      Attribute::Symbol{Reference(ResourceName({}, ResourceType::kId, name)), value}); +  return *this; +} + +std::unique_ptr<Attribute> AttributeBuilder::Build() { +  return std::move(attr_); +} + +StyleBuilder& StyleBuilder::SetParent(const StringPiece& str) { +  style_->parent = Reference(ParseNameOrDie(str)); +  return *this; +} + +StyleBuilder& StyleBuilder::AddItem(const StringPiece& str, std::unique_ptr<Item> value) { +  style_->entries.push_back(Style::Entry{Reference(ParseNameOrDie(str)), std::move(value)}); +  return *this; +} + +StyleBuilder& StyleBuilder::AddItem(const StringPiece& str, const ResourceId& id, +                                    std::unique_ptr<Item> value) { +  AddItem(str, std::move(value)); +  style_->entries.back().key.id = id; +  return *this; +} + +std::unique_ptr<Style> StyleBuilder::Build() { +  return std::move(style_); +} + +StyleableBuilder& StyleableBuilder::AddItem(const StringPiece& str, const Maybe<ResourceId>& id) { +  styleable_->entries.push_back(Reference(ParseNameOrDie(str))); +  styleable_->entries.back().id = id; +  return *this; +} + +std::unique_ptr<Styleable> StyleableBuilder::Build() { +  return std::move(styleable_); +} + +std::unique_ptr<xml::XmlResource> BuildXmlDom(const StringPiece& str) { +  std::string input = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; +  input.append(str.data(), str.size()); +  StringInputStream in(input); +  StdErrDiagnostics diag; +  std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, &diag, Source("test.xml")); +  CHECK(doc != nullptr && doc->root != nullptr) << "failed to parse inline XML string"; +  return doc; +} + +std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context, +                                                            const StringPiece& str) { +  std::unique_ptr<xml::XmlResource> doc = BuildXmlDom(str); +  doc->file.name.package = context->GetCompilationPackage(); +  return doc; +} + +}  // namespace test +}  // namespace aapt diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index 6b8207647471..d9f3912fb4c6 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -19,13 +19,13 @@  #include <memory> -#include "android-base/logging.h"  #include "android-base/macros.h" +#include "Resource.h"  #include "ResourceTable.h"  #include "ResourceValues.h" -#include "test/Common.h" -#include "util/Util.h" +#include "process/IResourceTableConsumer.h" +#include "util/Maybe.h"  #include "xml/XmlDom.h"  namespace aapt { @@ -35,97 +35,37 @@ class ResourceTableBuilder {   public:    ResourceTableBuilder() = default; -  StringPool* string_pool() { return &table_->string_pool; } - -  ResourceTableBuilder& SetPackageId(const android::StringPiece& package_name, uint8_t id) { -    ResourceTablePackage* package = table_->CreatePackage(package_name, id); -    CHECK(package != nullptr); -    return *this; -  } - -  ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ResourceId& id = {}) { -    return AddValue(name, id, util::make_unique<Id>()); -  } - +  ResourceTableBuilder& SetPackageId(const android::StringPiece& package_name, uint8_t id); +  ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ResourceId& id = {});    ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ConfigDescription& config, -                                  const ResourceId& id = {}) { -    return AddValue(name, config, id, util::make_unique<Id>()); -  } - +                                  const ResourceId& id = {});    ResourceTableBuilder& AddReference(const android::StringPiece& name, -                                     const android::StringPiece& ref) { -    return AddReference(name, {}, ref); -  } - +                                     const android::StringPiece& ref);    ResourceTableBuilder& AddReference(const android::StringPiece& name, const ResourceId& id, -                                     const android::StringPiece& ref) { -    return AddValue(name, id, util::make_unique<Reference>(ParseNameOrDie(ref))); -  } - +                                     const android::StringPiece& ref);    ResourceTableBuilder& AddString(const android::StringPiece& name, -                                  const android::StringPiece& str) { -    return AddString(name, {}, str); -  } - +                                  const android::StringPiece& str);    ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id, -                                  const android::StringPiece& str) { -    return AddValue( -        name, id, util::make_unique<String>(table_->string_pool.MakeRef(str))); -  } - +                                  const android::StringPiece& str);    ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id, -                                  const ConfigDescription& config, -                                  const android::StringPiece& str) { -    return AddValue(name, config, id, util::make_unique<String>( -                                          table_->string_pool.MakeRef(str))); -  } - +                                  const ConfigDescription& config, const android::StringPiece& str);    ResourceTableBuilder& AddFileReference(const android::StringPiece& name, -                                         const android::StringPiece& path) { -    return AddFileReference(name, {}, path); -  } - +                                         const android::StringPiece& path);    ResourceTableBuilder& AddFileReference(const android::StringPiece& name, const ResourceId& id, -                                         const android::StringPiece& path) { -    return AddValue(name, id, util::make_unique<FileReference>( -                                  table_->string_pool.MakeRef(path))); -  } - +                                         const android::StringPiece& path);    ResourceTableBuilder& AddFileReference(const android::StringPiece& name,                                           const android::StringPiece& path, -                                         const ConfigDescription& config) { -    return AddValue(name, config, {}, util::make_unique<FileReference>( -                                          table_->string_pool.MakeRef(path))); -  } - -  ResourceTableBuilder& AddValue(const android::StringPiece& name, std::unique_ptr<Value> value) { -    return AddValue(name, {}, std::move(value)); -  } - +                                         const ConfigDescription& config); +  ResourceTableBuilder& AddValue(const android::StringPiece& name, std::unique_ptr<Value> value);    ResourceTableBuilder& AddValue(const android::StringPiece& name, const ResourceId& id, -                                 std::unique_ptr<Value> value) { -    return AddValue(name, {}, id, std::move(value)); -  } - +                                 std::unique_ptr<Value> value);    ResourceTableBuilder& AddValue(const android::StringPiece& name, const ConfigDescription& config, -                                 const ResourceId& id, std::unique_ptr<Value> value) { -    ResourceName res_name = ParseNameOrDie(name); -    CHECK(table_->AddResourceAllowMangled(res_name, id, config, {}, std::move(value), -                                          GetDiagnostics())); -    return *this; -  } - +                                 const ResourceId& id, std::unique_ptr<Value> value);    ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id, -                                       SymbolState state, bool allow_new = false) { -    ResourceName res_name = ParseNameOrDie(name); -    Symbol symbol; -    symbol.state = state; -    symbol.allow_new = allow_new; -    CHECK(table_->SetSymbolStateAllowMangled(res_name, id, symbol, GetDiagnostics())); -    return *this; -  } +                                       SymbolState state, bool allow_new = false); -  std::unique_ptr<ResourceTable> Build() { return std::move(table_); } +  StringPool* string_pool(); +  std::unique_ptr<ResourceTable> Build();   private:    DISALLOW_COPY_AND_ASSIGN(ResourceTableBuilder); @@ -133,29 +73,16 @@ class ResourceTableBuilder {    std::unique_ptr<ResourceTable> table_ = util::make_unique<ResourceTable>();  }; -inline std::unique_ptr<Reference> BuildReference(const android::StringPiece& ref, -                                                 const Maybe<ResourceId>& id = {}) { -  std::unique_ptr<Reference> reference = -      util::make_unique<Reference>(ParseNameOrDie(ref)); -  reference->id = id; -  return reference; -} - -inline std::unique_ptr<BinaryPrimitive> BuildPrimitive(uint8_t type, -                                                       uint32_t data) { -  android::Res_value value = {}; -  value.size = sizeof(value); -  value.dataType = type; -  value.data = data; -  return util::make_unique<BinaryPrimitive>(value); -} +std::unique_ptr<Reference> BuildReference(const android::StringPiece& ref, +                                          const Maybe<ResourceId>& id = {}); +std::unique_ptr<BinaryPrimitive> BuildPrimitive(uint8_t type, uint32_t data);  template <typename T>  class ValueBuilder {   public:    template <typename... Args> -  explicit ValueBuilder(Args&&... args) -      : value_(new T{std::forward<Args>(args)...}) {} +  explicit ValueBuilder(Args&&... args) : value_(new T{std::forward<Args>(args)...}) { +  }    template <typename... Args>    ValueBuilder& SetSource(Args&&... args) { @@ -168,7 +95,9 @@ class ValueBuilder {      return *this;    } -  std::unique_ptr<Value> Build() { return std::move(value_); } +  std::unique_ptr<Value> Build() { +    return std::move(value_); +  }   private:    DISALLOW_COPY_AND_ASSIGN(ValueBuilder); @@ -178,23 +107,10 @@ class ValueBuilder {  class AttributeBuilder {   public: -  explicit AttributeBuilder(bool weak = false) -      : attr_(util::make_unique<Attribute>(weak)) { -    attr_->type_mask = android::ResTable_map::TYPE_ANY; -  } - -  AttributeBuilder& SetTypeMask(uint32_t typeMask) { -    attr_->type_mask = typeMask; -    return *this; -  } - -  AttributeBuilder& AddItem(const android::StringPiece& name, uint32_t value) { -    attr_->symbols.push_back(Attribute::Symbol{ -        Reference(ResourceName({}, ResourceType::kId, name)), value}); -    return *this; -  } - -  std::unique_ptr<Attribute> Build() { return std::move(attr_); } +  explicit AttributeBuilder(bool weak = false); +  AttributeBuilder& SetTypeMask(uint32_t typeMask); +  AttributeBuilder& AddItem(const android::StringPiece& name, uint32_t value); +  std::unique_ptr<Attribute> Build();   private:    DISALLOW_COPY_AND_ASSIGN(AttributeBuilder); @@ -205,27 +121,11 @@ class AttributeBuilder {  class StyleBuilder {   public:    StyleBuilder() = default; - -  StyleBuilder& SetParent(const android::StringPiece& str) { -    style_->parent = Reference(ParseNameOrDie(str)); -    return *this; -  } - -  StyleBuilder& AddItem(const android::StringPiece& str, std::unique_ptr<Item> value) { -    style_->entries.push_back(Style::Entry{Reference(ParseNameOrDie(str)), std::move(value)}); -    return *this; -  } - +  StyleBuilder& SetParent(const android::StringPiece& str); +  StyleBuilder& AddItem(const android::StringPiece& str, std::unique_ptr<Item> value);    StyleBuilder& AddItem(const android::StringPiece& str, const ResourceId& id, -                        std::unique_ptr<Item> value) { -    AddItem(str, std::move(value)); -    style_->entries.back().key.id = id; -    return *this; -  } - -  std::unique_ptr<Style> Build() { -    return std::move(style_); -  } +                        std::unique_ptr<Item> value); +  std::unique_ptr<Style> Build();   private:    DISALLOW_COPY_AND_ASSIGN(StyleBuilder); @@ -236,14 +136,8 @@ class StyleBuilder {  class StyleableBuilder {   public:    StyleableBuilder() = default; - -  StyleableBuilder& AddItem(const android::StringPiece& str, const Maybe<ResourceId>& id = {}) { -    styleable_->entries.push_back(Reference(ParseNameOrDie(str))); -    styleable_->entries.back().id = id; -    return *this; -  } - -  std::unique_ptr<Styleable> Build() { return std::move(styleable_); } +  StyleableBuilder& AddItem(const android::StringPiece& str, const Maybe<ResourceId>& id = {}); +  std::unique_ptr<Styleable> Build();   private:    DISALLOW_COPY_AND_ASSIGN(StyleableBuilder); @@ -251,22 +145,9 @@ class StyleableBuilder {    std::unique_ptr<Styleable> styleable_ = util::make_unique<Styleable>();  }; -inline std::unique_ptr<xml::XmlResource> BuildXmlDom(const android::StringPiece& str) { -  std::stringstream in; -  in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str; -  StdErrDiagnostics diag; -  std::unique_ptr<xml::XmlResource> doc = -      xml::Inflate(&in, &diag, Source("test.xml")); -  CHECK(doc != nullptr) << "failed to parse inline XML string"; -  return doc; -} - -inline std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName( -    IAaptContext* context, const android::StringPiece& str) { -  std::unique_ptr<xml::XmlResource> doc = BuildXmlDom(str); -  doc->file.name.package = context->GetCompilationPackage(); -  return doc; -} +std::unique_ptr<xml::XmlResource> BuildXmlDom(const android::StringPiece& str); +std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context, +                                                            const android::StringPiece& str);  }  // namespace test  }  // namespace aapt diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h index d7b46caf8c94..e6b38c0007b4 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -142,10 +142,97 @@ MATCHER_P(StrEq, a,    return android::StringPiece16(arg) == a;  } -MATCHER_P(ValueEq, a, -          std::string(negation ? "isn't" : "is") + " equal to " + ::testing::PrintToString(a)) { -  return arg.Equals(&a); -} +class ValueEq { + public: +  template <typename arg_type> +  class BaseImpl : public ::testing::MatcherInterface<arg_type> { +    BaseImpl(const BaseImpl&) = default; + +    void DescribeTo(::std::ostream* os) const override { +      *os << "is equal to " << *expected_; +    } + +    void DescribeNegationTo(::std::ostream* os) const override { +      *os << "is not equal to " << *expected_; +    } + +   protected: +    BaseImpl(const Value* expected) : expected_(expected) { +    } + +    const Value* expected_; +  }; + +  template <typename T, bool> +  class Impl {}; + +  template <typename T> +  class Impl<T, false> : public ::testing::MatcherInterface<T> { +   public: +    explicit Impl(const Value* expected) : expected_(expected) { +    } + +    bool MatchAndExplain(T x, ::testing::MatchResultListener* listener) const override { +      return expected_->Equals(&x); +    } + +    void DescribeTo(::std::ostream* os) const override { +      *os << "is equal to " << *expected_; +    } + +    void DescribeNegationTo(::std::ostream* os) const override { +      *os << "is not equal to " << *expected_; +    } + +   private: +    DISALLOW_COPY_AND_ASSIGN(Impl); + +    const Value* expected_; +  }; + +  template <typename T> +  class Impl<T, true> : public ::testing::MatcherInterface<T> { +   public: +    explicit Impl(const Value* expected) : expected_(expected) { +    } + +    bool MatchAndExplain(T x, ::testing::MatchResultListener* listener) const override { +      return expected_->Equals(x); +    } + +    void DescribeTo(::std::ostream* os) const override { +      *os << "is equal to " << *expected_; +    } + +    void DescribeNegationTo(::std::ostream* os) const override { +      *os << "is not equal to " << *expected_; +    } + +   private: +    DISALLOW_COPY_AND_ASSIGN(Impl); + +    const Value* expected_; +  }; + +  ValueEq(const Value& expected) : expected_(&expected) { +  } +  ValueEq(const Value* expected) : expected_(expected) { +  } +  ValueEq(const ValueEq&) = default; + +  template <typename T> +  operator ::testing::Matcher<T>() const { +    return ::testing::Matcher<T>(new Impl<T, std::is_pointer<T>::value>(expected_)); +  } + + private: +  const Value* expected_; +}; + +// MATCHER_P(ValueEq, a, +//          std::string(negation ? "isn't" : "is") + " equal to " + ::testing::PrintToString(a)) { +//  return arg.Equals(&a); +//}  MATCHER_P(StrValueEq, a,            std::string(negation ? "isn't" : "is") + " equal to " + ::testing::PrintToString(a)) { diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp index 728d1f4207c4..892aee6fadcb 100644 --- a/tools/aapt2/unflatten/BinaryResourceParser.cpp +++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp @@ -544,7 +544,7 @@ std::unique_ptr<Array> BinaryResourceParser::ParseArray(      const ResTable_map_entry* map) {    std::unique_ptr<Array> array = util::make_unique<Array>();    for (const ResTable_map& map_entry : map) { -    array->items.push_back(ParseValue(name, config, map_entry.value)); +    array->elements.push_back(ParseValue(name, config, map_entry.value));    }    return array;  } diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp index 1bf25947ea93..bf8dc4d727f7 100644 --- a/tools/aapt2/util/Files.cpp +++ b/tools/aapt2/util/Files.cpp @@ -27,6 +27,8 @@  #include "android-base/errors.h"  #include "android-base/file.h"  #include "android-base/logging.h" +#include "android-base/unique_fd.h" +#include "android-base/utf8.h"  #include "util/Util.h" @@ -35,14 +37,32 @@  #include <direct.h>  #endif -using android::StringPiece; +using ::android::FileMap; +using ::android::StringPiece; +using ::android::base::ReadFileToString; +using ::android::base::SystemErrorCodeToString; +using ::android::base::unique_fd;  namespace aapt {  namespace file { -FileType GetFileType(const StringPiece& path) { +FileType GetFileType(const std::string& path) { +// TODO(adamlesinski): I'd like to move this to ::android::base::utf8 but Windows does some macro +// trickery with 'stat' and things don't override very well. +#ifdef _WIN32 +  std::wstring path_utf16; +  if (!::android::base::UTF8PathToWindowsLongPath(path.c_str(), &path_utf16)) { +    return FileType::kNonexistant; +  } + +  struct _stat64 sb; +  int result = _wstat64(path_utf16.c_str(), &sb); +#else    struct stat sb; -  if (stat(path.data(), &sb) < 0) { +  int result = stat(path.c_str(), &sb); +#endif + +  if (result == -1) {      if (errno == ENOENT || errno == ENOTDIR) {        return FileType::kNonexistant;      } @@ -72,27 +92,20 @@ FileType GetFileType(const StringPiece& path) {    }  } -inline static int MkdirImpl(const StringPiece& path) { -#ifdef _WIN32 -  return _mkdir(path.to_string().c_str()); -#else -  return mkdir(path.to_string().c_str(), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP); -#endif -} - -bool mkdirs(const StringPiece& path) { -  const char* start = path.begin(); -  const char* end = path.end(); -  for (const char* current = start; current != end; ++current) { -    if (*current == sDirSep && current != start) { -      StringPiece parent_path(start, current - start); -      int result = MkdirImpl(parent_path); -      if (result < 0 && errno != EEXIST) { -        return false; -      } +bool mkdirs(const std::string& path) { +  constexpr const mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP; +  // Start after the first character so that we don't consume the root '/'. +  // This is safe to do with unicode because '/' will never match with a continuation character. +  size_t current_pos = 1u; +  while ((current_pos = path.find(sDirSep, current_pos)) != std::string::npos) { +    std::string parent_path = path.substr(0, current_pos); +    int result = ::android::base::utf8::mkdir(parent_path.c_str(), mode); +    if (result < 0 && errno != EEXIST) { +      return false;      } +    current_pos += 1;    } -  return MkdirImpl(path) == 0 || errno == EEXIST; +  return ::android::base::utf8::mkdir(path.c_str(), mode) == 0 || errno == EEXIST;  }  StringPiece GetStem(const StringPiece& path) { @@ -129,10 +142,8 @@ StringPiece GetExtension(const StringPiece& path) {  void AppendPath(std::string* base, StringPiece part) {    CHECK(base != nullptr); -  const bool base_has_trailing_sep = -      (!base->empty() && *(base->end() - 1) == sDirSep); -  const bool part_has_leading_sep = -      (!part.empty() && *(part.begin()) == sDirSep); +  const bool base_has_trailing_sep = (!base->empty() && *(base->end() - 1) == sDirSep); +  const bool part_has_leading_sep = (!part.empty() && *(part.begin()) == sDirSep);    if (base_has_trailing_sep && part_has_leading_sep) {      // Remove the part's leading sep      part = part.substr(1, part.size() - 1); @@ -151,31 +162,34 @@ std::string PackageToPath(const StringPiece& package) {    return out_path;  } -Maybe<android::FileMap> MmapPath(const StringPiece& path, -                                 std::string* out_error) { -  std::unique_ptr<FILE, decltype(fclose)*> f = {fopen(path.data(), "rb"), -                                                fclose}; -  if (!f) { -    if (out_error) *out_error = android::base::SystemErrorCodeToString(errno); +Maybe<FileMap> MmapPath(const std::string& path, std::string* out_error) { +  int flags = O_RDONLY | O_CLOEXEC | O_BINARY; +  unique_fd fd(TEMP_FAILURE_RETRY(::android::base::utf8::open(path.c_str(), flags))); +  if (fd == -1) { +    if (out_error) { +      *out_error = SystemErrorCodeToString(errno); +    }      return {};    } -  int fd = fileno(f.get()); -    struct stat filestats = {};    if (fstat(fd, &filestats) != 0) { -    if (out_error) *out_error = android::base::SystemErrorCodeToString(errno); +    if (out_error) { +      *out_error = SystemErrorCodeToString(errno); +    }      return {};    } -  android::FileMap filemap; +  FileMap filemap;    if (filestats.st_size == 0) {      // mmap doesn't like a length of 0. Instead we return an empty FileMap.      return std::move(filemap);    } -  if (!filemap.create(path.data(), fd, 0, filestats.st_size, true)) { -    if (out_error) *out_error = android::base::SystemErrorCodeToString(errno); +  if (!filemap.create(path.c_str(), fd, 0, filestats.st_size, true)) { +    if (out_error) { +      *out_error = SystemErrorCodeToString(errno); +    }      return {};    }    return std::move(filemap); @@ -184,7 +198,7 @@ Maybe<android::FileMap> MmapPath(const StringPiece& path,  bool AppendArgsFromFile(const StringPiece& path, std::vector<std::string>* out_arglist,                          std::string* out_error) {    std::string contents; -  if (!android::base::ReadFileToString(path.to_string(), &contents, true /*follow_symlinks*/)) { +  if (!ReadFileToString(path.to_string(), &contents, true /*follow_symlinks*/)) {      if (out_error) {        *out_error = "failed to read argument-list file";      } @@ -270,7 +284,7 @@ Maybe<std::vector<std::string>> FindFiles(const android::StringPiece& path, IDia    const std::string root_dir = path.to_string();    std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);    if (!d) { -    diag->Error(DiagMessage() << android::base::SystemErrorCodeToString(errno)); +    diag->Error(DiagMessage() << SystemErrorCodeToString(errno));      return {};    } diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h index b3b1e484d27b..b26e4fa26de6 100644 --- a/tools/aapt2/util/Files.h +++ b/tools/aapt2/util/Files.h @@ -34,8 +34,10 @@ namespace file {  #ifdef _WIN32  constexpr const char sDirSep = '\\'; +constexpr const char sPathSep = ';';  #else  constexpr const char sDirSep = '/'; +constexpr const char sPathSep = ':';  #endif  enum class FileType { @@ -50,79 +52,56 @@ enum class FileType {    kSocket,  }; -FileType GetFileType(const android::StringPiece& path); +FileType GetFileType(const std::string& path); -/* - * Appends a path to `base`, separated by the directory separator. - */ +// Appends a path to `base`, separated by the directory separator.  void AppendPath(std::string* base, android::StringPiece part); -/* - * Makes all the directories in `path`. The last element in the path - * is interpreted as a directory. - */ -bool mkdirs(const android::StringPiece& path); +// Makes all the directories in `path`. The last element in the path is interpreted as a directory. +bool mkdirs(const std::string& path); -/** - * Returns all but the last part of the path. - */ +// Returns all but the last part of the path.  android::StringPiece GetStem(const android::StringPiece& path); -/** - * Returns the last part of the path with extension. - */ +// Returns the last part of the path with extension.  android::StringPiece GetFilename(const android::StringPiece& path); -/** - * Returns the extension of the path. This is the entire string after - * the first '.' of the last part of the path. - */ +// Returns the extension of the path. This is the entire string after the first '.' of the last part +// of the path.  android::StringPiece GetExtension(const android::StringPiece& path); -/** - * Converts a package name (com.android.app) to a path: com/android/app - */ +// Converts a package name (com.android.app) to a path: com/android/app  std::string PackageToPath(const android::StringPiece& package); -/** - * Creates a FileMap for the file at path. - */ -Maybe<android::FileMap> MmapPath(const android::StringPiece& path, std::string* out_error); +// Creates a FileMap for the file at path. +Maybe<android::FileMap> MmapPath(const std::string& path, std::string* out_error); -/** - * Reads the file at path and appends each line to the outArgList vector. - */ +// Reads the file at path and appends each line to the outArgList vector.  bool AppendArgsFromFile(const android::StringPiece& path, std::vector<std::string>* out_arglist,                          std::string* out_error); -/* - * Filter that determines which resource files/directories are - * processed by AAPT. Takes a pattern string supplied by the user. - * Pattern format is specified in the FileFilter::SetPattern() method. - */ +// Filter that determines which resource files/directories are +// processed by AAPT. Takes a pattern string supplied by the user. +// Pattern format is specified in the FileFilter::SetPattern() method.  class FileFilter {   public:    explicit FileFilter(IDiagnostics* diag) : diag_(diag) {} -  /* -   * Patterns syntax: -   * - Delimiter is : -   * - Entry can start with the flag ! to avoid printing a warning -   *   about the file being ignored. -   * - Entry can have the flag "<dir>" to match only directories -   *   or <file> to match only files. Default is to match both. -   * - Entry can be a simplified glob "<prefix>*" or "*<suffix>" -   *   where prefix/suffix must have at least 1 character (so that -   *   we don't match a '*' catch-all pattern.) -   * - The special filenames "." and ".." are always ignored. -   * - Otherwise the full string is matched. -   * - match is not case-sensitive. -   */ +  // Patterns syntax: +  // - Delimiter is : +  // - Entry can start with the flag ! to avoid printing a warning +  //   about the file being ignored. +  // - Entry can have the flag "<dir>" to match only directories +  //   or <file> to match only files. Default is to match both. +  // - Entry can be a simplified glob "<prefix>*" or "*<suffix>" +  //   where prefix/suffix must have at least 1 character (so that +  //   we don't match a '*' catch-all pattern.) +  // - The special filenames "." and ".." are always ignored. +  // - Otherwise the full string is matched. +  // - match is not case-sensitive.    bool SetPattern(const android::StringPiece& pattern); -  /** -   * Applies the filter, returning true for pass, false for fail. -   */ +  // Applies the filter, returning true for pass, false for fail.    bool operator()(const std::string& filename, FileType type) const;   private: diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index ad3989ea430f..8f021ab8cb8a 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -228,6 +228,12 @@ class Tokenizer {   public:    class iterator {     public: +    using reference = android::StringPiece&; +    using value_type = android::StringPiece; +    using difference_type = size_t; +    using pointer = android::StringPiece*; +    using iterator_category = std::forward_iterator_tag; +      iterator(const iterator&) = default;      iterator& operator=(const iterator&) = default; @@ -250,9 +256,13 @@ class Tokenizer {    Tokenizer(android::StringPiece str, char sep); -  iterator begin() { return begin_; } +  iterator begin() const { +    return begin_; +  } -  iterator end() { return end_; } +  iterator end() const { +    return end_; +  }   private:    const iterator begin_; diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp index 352a93361633..cc664a5de722 100644 --- a/tools/aapt2/xml/XmlActionExecutor.cpp +++ b/tools/aapt2/xml/XmlActionExecutor.cpp @@ -78,7 +78,7 @@ bool XmlActionExecutor::Execute(XmlActionExecutorPolicy policy, IDiagnostics* di                                  XmlResource* doc) const {    SourcePathDiagnostics source_diag(doc->file.source, diag); -  Element* el = FindRootElement(doc); +  Element* el = doc->root.get();    if (!el) {      if (policy == XmlActionExecutorPolicy::kWhitelist) {        source_diag.Error(DiagMessage() << "no root XML tag found"); diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp index 885ab3e33fed..cbb652ed9a1a 100644 --- a/tools/aapt2/xml/XmlDom.cpp +++ b/tools/aapt2/xml/XmlDom.cpp @@ -29,8 +29,9 @@  #include "XmlPullParser.h"  #include "util/Util.h" -using android::StringPiece; -using android::StringPiece16; +using ::aapt::io::InputStream; +using ::android::StringPiece; +using ::android::StringPiece16;  namespace aapt {  namespace xml { @@ -38,17 +39,15 @@ namespace xml {  constexpr char kXmlNamespaceSep = 1;  struct Stack { -  std::unique_ptr<xml::Node> root; -  std::stack<xml::Node*> node_stack; +  std::unique_ptr<xml::Element> root; +  std::stack<xml::Element*> node_stack; +  std::unique_ptr<xml::Element> pending_element;    std::string pending_comment;    std::unique_ptr<xml::Text> last_text_node;  }; -/** - * Extracts the namespace and name of an expanded element or attribute name. - */ -static void SplitName(const char* name, std::string* out_ns, -                      std::string* out_name) { +// Extracts the namespace and name of an expanded element or attribute name. +static void SplitName(const char* name, std::string* out_ns, std::string* out_name) {    const char* p = name;    while (*p != 0 && *p != kXmlNamespaceSep) {      p++; @@ -66,6 +65,7 @@ static void SplitName(const char* name, std::string* out_ns,  static void FinishPendingText(Stack* stack) {    if (stack->last_text_node != nullptr) {      if (!stack->last_text_node->text.empty()) { +      CHECK(!stack->node_stack.empty());        stack->node_stack.top()->AppendChild(std::move(stack->last_text_node));      } else {        // Drop an empty text node. @@ -74,48 +74,27 @@ static void FinishPendingText(Stack* stack) {    }  } -static void AddToStack(Stack* stack, XML_Parser parser, -                       std::unique_ptr<Node> node) { -  node->line_number = XML_GetCurrentLineNumber(parser); -  node->column_number = XML_GetCurrentColumnNumber(parser); - -  Node* this_node = node.get(); -  if (!stack->node_stack.empty()) { -    stack->node_stack.top()->AppendChild(std::move(node)); -  } else { -    stack->root = std::move(node); -  } - -  if (!NodeCast<Text>(this_node)) { -    stack->node_stack.push(this_node); -  } -} - -static void XMLCALL StartNamespaceHandler(void* user_data, const char* prefix, -                                          const char* uri) { +static void XMLCALL StartNamespaceHandler(void* user_data, const char* prefix, const char* uri) {    XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);    Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));    FinishPendingText(stack); -  std::unique_ptr<Namespace> ns = util::make_unique<Namespace>(); -  if (prefix) { -    ns->namespace_prefix = prefix; -  } +  NamespaceDecl decl; +  decl.line_number = XML_GetCurrentLineNumber(parser); +  decl.column_number = XML_GetCurrentColumnNumber(parser); +  decl.prefix = prefix ? prefix : ""; +  decl.uri = uri ? uri : ""; -  if (uri) { -    ns->namespace_uri = uri; +  if (stack->pending_element == nullptr) { +    stack->pending_element = util::make_unique<Element>();    } - -  AddToStack(stack, parser, std::move(ns)); +  stack->pending_element->namespace_decls.push_back(std::move(decl));  } -static void XMLCALL EndNamespaceHandler(void* user_data, const char* prefix) { +static void XMLCALL EndNamespaceHandler(void* user_data, const char* /*prefix*/) {    XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);    Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));    FinishPendingText(stack); - -  CHECK(!stack->node_stack.empty()); -  stack->node_stack.pop();  }  static bool less_attribute(const Attribute& lhs, const Attribute& rhs) { @@ -123,28 +102,42 @@ static bool less_attribute(const Attribute& lhs, const Attribute& rhs) {           std::tie(rhs.namespace_uri, rhs.name, rhs.value);  } -static void XMLCALL StartElementHandler(void* user_data, const char* name, -                                        const char** attrs) { +static void XMLCALL StartElementHandler(void* user_data, const char* name, const char** attrs) {    XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);    Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));    FinishPendingText(stack); -  std::unique_ptr<Element> el = util::make_unique<Element>(); +  std::unique_ptr<Element> el; +  if (stack->pending_element != nullptr) { +    el = std::move(stack->pending_element); +  } else { +    el = util::make_unique<Element>(); +  } + +  el->line_number = XML_GetCurrentLineNumber(parser); +  el->column_number = XML_GetCurrentColumnNumber(parser); +  el->comment = std::move(stack->pending_comment); +    SplitName(name, &el->namespace_uri, &el->name);    while (*attrs) {      Attribute attribute;      SplitName(*attrs++, &attribute.namespace_uri, &attribute.name);      attribute.value = *attrs++; - -    // Insert in sorted order. -    auto iter = std::lower_bound(el->attributes.begin(), el->attributes.end(), attribute, -                                 less_attribute); -    el->attributes.insert(iter, std::move(attribute)); +    el->attributes.push_back(std::move(attribute));    } -  el->comment = std::move(stack->pending_comment); -  AddToStack(stack, parser, std::move(el)); +  // Sort the attributes. +  std::sort(el->attributes.begin(), el->attributes.end(), less_attribute); + +  // Add to the stack. +  Element* this_el = el.get(); +  if (!stack->node_stack.empty()) { +    stack->node_stack.top()->AppendChild(std::move(el)); +  } else { +    stack->root = std::move(el); +  } +  stack->node_stack.push(this_el);  }  static void XMLCALL EndElementHandler(void* user_data, const char* name) { @@ -189,40 +182,41 @@ static void XMLCALL CommentDataHandler(void* user_data, const char* comment) {    stack->pending_comment += comment;  } -std::unique_ptr<XmlResource> Inflate(std::istream* in, IDiagnostics* diag, const Source& source) { +std::unique_ptr<XmlResource> Inflate(InputStream* in, IDiagnostics* diag, const Source& source) {    Stack stack; -  XML_Parser parser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep); -  XML_SetUserData(parser, &stack); -  XML_UseParserAsHandlerArg(parser); -  XML_SetElementHandler(parser, StartElementHandler, EndElementHandler); -  XML_SetNamespaceDeclHandler(parser, StartNamespaceHandler, EndNamespaceHandler); -  XML_SetCharacterDataHandler(parser, CharacterDataHandler); -  XML_SetCommentHandler(parser, CommentDataHandler); - -  char buffer[1024]; -  while (!in->eof()) { -    in->read(buffer, sizeof(buffer) / sizeof(buffer[0])); -    if (in->bad() && !in->eof()) { -      stack.root = {}; -      diag->Error(DiagMessage(source) << strerror(errno)); -      break; -    } - -    if (XML_Parse(parser, buffer, in->gcount(), in->eof()) == XML_STATUS_ERROR) { -      stack.root = {}; -      diag->Error(DiagMessage(source.WithLine(XML_GetCurrentLineNumber(parser))) -                  << XML_ErrorString(XML_GetErrorCode(parser))); -      break; +  std::unique_ptr<std::remove_pointer<XML_Parser>::type, decltype(XML_ParserFree)*> parser = { +      XML_ParserCreateNS(nullptr, kXmlNamespaceSep), XML_ParserFree}; +  XML_SetUserData(parser.get(), &stack); +  XML_UseParserAsHandlerArg(parser.get()); +  XML_SetElementHandler(parser.get(), StartElementHandler, EndElementHandler); +  XML_SetNamespaceDeclHandler(parser.get(), StartNamespaceHandler, EndNamespaceHandler); +  XML_SetCharacterDataHandler(parser.get(), CharacterDataHandler); +  XML_SetCommentHandler(parser.get(), CommentDataHandler); + +  const char* buffer = nullptr; +  size_t buffer_size = 0; +  while (in->Next(reinterpret_cast<const void**>(&buffer), &buffer_size)) { +    if (XML_Parse(parser.get(), buffer, buffer_size, false) == XML_STATUS_ERROR) { +      diag->Error(DiagMessage(source.WithLine(XML_GetCurrentLineNumber(parser.get()))) +                  << XML_ErrorString(XML_GetErrorCode(parser.get()))); +      return {};      }    } -  XML_ParserFree(parser); -  if (stack.root) { -    return util::make_unique<XmlResource>(ResourceFile{{}, {}, source}, StringPool{}, -                                          std::move(stack.root)); +  if (in->HadError()) { +    diag->Error(DiagMessage(source) << in->GetError()); +    return {}; +  } else { +    // Finish off the parsing. +    if (XML_Parse(parser.get(), nullptr, 0u, true) == XML_STATUS_ERROR) { +      diag->Error(DiagMessage(source.WithLine(XML_GetCurrentLineNumber(parser.get()))) +                  << XML_ErrorString(XML_GetErrorCode(parser.get()))); +      return {}; +    }    } -  return {}; +  return util::make_unique<XmlResource>(ResourceFile{{}, {}, source}, StringPool{}, +                                        std::move(stack.root));  }  static void CopyAttributes(Element* el, android::ResXMLParser* parser, StringPool* out_pool) { @@ -261,13 +255,13 @@ static void CopyAttributes(Element* el, android::ResXMLParser* parser, StringPoo  std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnostics* diag,                                       const Source& source) {    // We import the android namespace because on Windows NO_ERROR is a macro, not -  // an enum, which -  // causes errors when qualifying it with android:: +  // an enum, which causes errors when qualifying it with android::    using namespace android;    StringPool string_pool; -  std::unique_ptr<Node> root; -  std::stack<Node*> node_stack; +  std::unique_ptr<Element> root; +  std::stack<Element*> node_stack; +  std::unique_ptr<Element> pending_element;    ResXMLTree tree;    if (tree.setTo(data, data_len) != NO_ERROR) { @@ -275,57 +269,76 @@ std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnos    }    ResXMLParser::event_code_t code; -  while ((code = tree.next()) != ResXMLParser::BAD_DOCUMENT && -         code != ResXMLParser::END_DOCUMENT) { +  while ((code = tree.next()) != ResXMLParser::BAD_DOCUMENT && code != ResXMLParser::END_DOCUMENT) {      std::unique_ptr<Node> new_node;      switch (code) {        case ResXMLParser::START_NAMESPACE: { -        std::unique_ptr<Namespace> node = util::make_unique<Namespace>(); +        NamespaceDecl decl;          size_t len;          const char16_t* str16 = tree.getNamespacePrefix(&len);          if (str16) { -          node->namespace_prefix = util::Utf16ToUtf8(StringPiece16(str16, len)); +          decl.prefix = util::Utf16ToUtf8(StringPiece16(str16, len));          }          str16 = tree.getNamespaceUri(&len);          if (str16) { -          node->namespace_uri = util::Utf16ToUtf8(StringPiece16(str16, len)); +          decl.uri = util::Utf16ToUtf8(StringPiece16(str16, len)); +        } + +        if (pending_element == nullptr) { +          pending_element = util::make_unique<Element>();          } -        new_node = std::move(node);          break;        }        case ResXMLParser::START_TAG: { -        std::unique_ptr<Element> node = util::make_unique<Element>(); +        std::unique_ptr<Element> el; +        if (pending_element != nullptr) { +          el = std::move(pending_element); +        } else { +          el = util::make_unique<Element>(); +          ; +        } +          size_t len;          const char16_t* str16 = tree.getElementNamespace(&len);          if (str16) { -          node->namespace_uri = util::Utf16ToUtf8(StringPiece16(str16, len)); +          el->namespace_uri = util::Utf16ToUtf8(StringPiece16(str16, len));          }          str16 = tree.getElementName(&len);          if (str16) { -          node->name = util::Utf16ToUtf8(StringPiece16(str16, len)); +          el->name = util::Utf16ToUtf8(StringPiece16(str16, len));          } -        CopyAttributes(node.get(), &tree, &string_pool); +        Element* this_el = el.get(); +        CopyAttributes(el.get(), &tree, &string_pool); -        new_node = std::move(node); +        if (!node_stack.empty()) { +          node_stack.top()->AppendChild(std::move(el)); +        } else { +          root = std::move(el); +        } +        node_stack.push(this_el);          break;        }        case ResXMLParser::TEXT: { -        std::unique_ptr<Text> node = util::make_unique<Text>(); +        std::unique_ptr<Text> text = util::make_unique<Text>(); +        text->line_number = tree.getLineNumber();          size_t len;          const char16_t* str16 = tree.getText(&len);          if (str16) { -          node->text = util::Utf16ToUtf8(StringPiece16(str16, len)); +          text->text = util::Utf16ToUtf8(StringPiece16(str16, len));          } -        new_node = std::move(node); +        CHECK(!node_stack.empty()); +        node_stack.top()->AppendChild(std::move(text));          break;        }        case ResXMLParser::END_NAMESPACE: +        break; +        case ResXMLParser::END_TAG:          CHECK(!node_stack.empty());          node_stack.pop(); @@ -335,74 +348,32 @@ std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnos          LOG(FATAL) << "unhandled XML chunk type";          break;      } - -    if (new_node) { -      new_node->line_number = tree.getLineNumber(); - -      Node* this_node = new_node.get(); -      if (!root) { -        CHECK(node_stack.empty()) << "node stack should be empty"; -        root = std::move(new_node); -      } else { -        CHECK(!node_stack.empty()) << "node stack should not be empty"; -        node_stack.top()->AppendChild(std::move(new_node)); -      } - -      if (!NodeCast<Text>(this_node)) { -        node_stack.push(this_node); -      } -    }    }    return util::make_unique<XmlResource>(ResourceFile{}, std::move(string_pool), std::move(root));  } -std::unique_ptr<Node> Namespace::Clone(const ElementCloneFunc& el_cloner) { -  auto ns = util::make_unique<Namespace>(); -  ns->comment = comment; -  ns->line_number = line_number; -  ns->column_number = column_number; -  ns->namespace_prefix = namespace_prefix; -  ns->namespace_uri = namespace_uri; -  ns->children.reserve(children.size()); -  for (const std::unique_ptr<xml::Node>& child : children) { -    ns->AppendChild(child->Clone(el_cloner)); -  } -  return std::move(ns); -} - -Element* FindRootElement(XmlResource* doc) { -  return FindRootElement(doc->root.get()); -} -  Element* FindRootElement(Node* node) { -  if (!node) { +  if (node == nullptr) {      return nullptr;    } -  Element* el = nullptr; -  while ((el = NodeCast<Element>(node)) == nullptr) { -    if (node->children.empty()) { -      return nullptr; -    } -    // We are looking for the first element, and namespaces can only have one -    // child. -    node = node->children.front().get(); +  while (node->parent != nullptr) { +    node = node->parent;    } -  return el; +  return NodeCast<Element>(node);  } -void Node::AppendChild(std::unique_ptr<Node> child) { +void Element::AppendChild(std::unique_ptr<Node> child) {    child->parent = this;    children.push_back(std::move(child));  } -void Node::InsertChild(size_t index, std::unique_ptr<Node> child) { +void Element::InsertChild(size_t index, std::unique_ptr<Node> child) {    child->parent = this;    children.insert(children.begin() + index, std::move(child));  } -Attribute* Element::FindAttribute(const StringPiece& ns, -                                  const StringPiece& name) { +Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) {    for (auto& attr : attributes) {      if (ns == attr.namespace_uri && name == attr.name) {        return &attr; @@ -424,21 +395,11 @@ Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) {    return FindChildWithAttribute(ns, name, {}, {}, {});  } -Element* Element::FindChildWithAttribute(const StringPiece& ns, -                                         const StringPiece& name, -                                         const StringPiece& attr_ns, -                                         const StringPiece& attr_name, +Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name, +                                         const StringPiece& attr_ns, const StringPiece& attr_name,                                           const StringPiece& attr_value) { -  for (auto& child_node : children) { -    Node* child = child_node.get(); -    while (NodeCast<Namespace>(child)) { -      if (child->children.empty()) { -        break; -      } -      child = child->children[0].get(); -    } - -    if (Element* el = NodeCast<Element>(child)) { +  for (auto& child : children) { +    if (Element* el = NodeCast<Element>(child.get())) {        if (ns == el->namespace_uri && name == el->name) {          if (attr_ns.empty() && attr_name.empty()) {            return el; @@ -457,23 +418,16 @@ Element* Element::FindChildWithAttribute(const StringPiece& ns,  std::vector<Element*> Element::GetChildElements() {    std::vector<Element*> elements;    for (auto& child_node : children) { -    Node* child = child_node.get(); -    while (NodeCast<Namespace>(child)) { -      if (child->children.empty()) { -        break; -      } -      child = child->children[0].get(); -    } - -    if (Element* el = NodeCast<Element>(child)) { -      elements.push_back(el); +    if (Element* child = NodeCast<Element>(child_node.get())) { +      elements.push_back(child);      }    }    return elements;  } -std::unique_ptr<Node> Element::Clone(const ElementCloneFunc& el_cloner) { +std::unique_ptr<Node> Element::Clone(const ElementCloneFunc& el_cloner) const {    auto el = util::make_unique<Element>(); +  el->namespace_decls = namespace_decls;    el->comment = comment;    el->line_number = line_number;    el->column_number = column_number; @@ -488,7 +442,17 @@ std::unique_ptr<Node> Element::Clone(const ElementCloneFunc& el_cloner) {    return std::move(el);  } -std::unique_ptr<Node> Text::Clone(const ElementCloneFunc&) { +std::unique_ptr<Element> Element::CloneElement(const ElementCloneFunc& el_cloner) const { +  return std::unique_ptr<Element>(static_cast<Element*>(Clone(el_cloner).release())); +} + +void Element::Accept(Visitor* visitor) { +  visitor->BeforeVisitElement(this); +  visitor->Visit(this); +  visitor->AfterVisitElement(this); +} + +std::unique_ptr<Node> Text::Clone(const ElementCloneFunc&) const {    auto t = util::make_unique<Text>();    t->comment = comment;    t->line_number = line_number; @@ -497,21 +461,22 @@ std::unique_ptr<Node> Text::Clone(const ElementCloneFunc&) {    return std::move(t);  } -void PackageAwareVisitor::Visit(Namespace* ns) { -  bool added = false; -  if (Maybe<ExtractedPackage> maybe_package = -          ExtractPackageFromNamespace(ns->namespace_uri)) { -    ExtractedPackage& package = maybe_package.value(); -    package_decls_.push_back( -        PackageDecl{ns->namespace_prefix, std::move(package)}); -    added = true; -  } - -  Visitor::Visit(ns); +void Text::Accept(Visitor* visitor) { +  visitor->Visit(this); +} -  if (added) { -    package_decls_.pop_back(); +void PackageAwareVisitor::BeforeVisitElement(Element* el) { +  std::vector<PackageDecl> decls; +  for (const NamespaceDecl& decl : el->namespace_decls) { +    if (Maybe<ExtractedPackage> maybe_package = ExtractPackageFromNamespace(decl.uri)) { +      decls.push_back(PackageDecl{decl.prefix, std::move(maybe_package.value())}); +    }    } +  package_decls_.push_back(std::move(decls)); +} + +void PackageAwareVisitor::AfterVisitElement(Element* el) { +  package_decls_.pop_back();  }  Maybe<ExtractedPackage> PackageAwareVisitor::TransformPackageAlias( @@ -522,11 +487,16 @@ Maybe<ExtractedPackage> PackageAwareVisitor::TransformPackageAlias(    const auto rend = package_decls_.rend();    for (auto iter = package_decls_.rbegin(); iter != rend; ++iter) { -    if (alias == iter->prefix) { -      if (iter->package.package.empty()) { -        return ExtractedPackage{local_package.to_string(), iter->package.private_namespace}; +    const std::vector<PackageDecl>& decls = *iter; +    const auto rend2 = decls.rend(); +    for (auto iter2 = decls.rbegin(); iter2 != rend2; ++iter2) { +      const PackageDecl& decl = *iter2; +      if (alias == decl.prefix) { +        if (decl.package.package.empty()) { +          return ExtractedPackage{local_package.to_string(), decl.package.private_namespace}; +        } +        return decl.package;        } -      return iter->package;      }    }    return {}; diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h index 2dc99d693148..154224381626 100644 --- a/tools/aapt2/xml/XmlDom.h +++ b/tools/aapt2/xml/XmlDom.h @@ -17,7 +17,6 @@  #ifndef AAPT_XML_DOM_H  #define AAPT_XML_DOM_H -#include <istream>  #include <memory>  #include <string>  #include <vector> @@ -27,58 +26,40 @@  #include "Diagnostics.h"  #include "Resource.h"  #include "ResourceValues.h" +#include "io/Io.h"  #include "util/Util.h"  #include "xml/XmlUtil.h"  namespace aapt {  namespace xml { -class RawVisitor; -  class Element; +class Visitor; -/** - * Base class for all XML nodes. - */ +// Base class for all XML nodes.  class Node {   public: -  Node* parent = nullptr; -  size_t line_number = 0; -  size_t column_number = 0; -  std::string comment; -  std::vector<std::unique_ptr<Node>> children; -    virtual ~Node() = default; -  void AppendChild(std::unique_ptr<Node> child); -  void InsertChild(size_t index, std::unique_ptr<Node> child); -  virtual void Accept(RawVisitor* visitor) = 0; +  Element* parent = nullptr; +  size_t line_number = 0u; +  size_t column_number = 0u; +  std::string comment; + +  virtual void Accept(Visitor* visitor) = 0;    using ElementCloneFunc = std::function<void(const Element&, Element*)>;    // Clones the Node subtree, using the given function to decide how to clone an Element. -  virtual std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) = 0; +  virtual std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) const = 0;  }; -/** - * Base class that implements the visitor methods for a - * subclass of Node. - */ -template <typename Derived> -class BaseNode : public Node { - public: -  virtual void Accept(RawVisitor* visitor) override; -}; - -/** - * A Namespace XML node. Can only have one child. - */ -class Namespace : public BaseNode<Namespace> { - public: -  std::string namespace_prefix; -  std::string namespace_uri; - -  std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) override; +// A namespace declaration (xmlns:prefix="uri"). +struct NamespaceDecl { +  std::string prefix; +  std::string uri; +  size_t line_number = 0u; +  size_t column_number = 0u;  };  struct AaptAttribute { @@ -90,9 +71,7 @@ struct AaptAttribute {    Maybe<ResourceId> id;  }; -/** - * An XML attribute. - */ +// An XML attribute.  struct Attribute {    std::string namespace_uri;    std::string name; @@ -102,41 +81,50 @@ struct Attribute {    std::unique_ptr<Item> compiled_value;  }; -/** - * An Element XML node. - */ -class Element : public BaseNode<Element> { +// An Element XML node. +class Element : public Node {   public: +  // Ordered namespace prefix declarations. +  std::vector<NamespaceDecl> namespace_decls; +    std::string namespace_uri;    std::string name;    std::vector<Attribute> attributes; +  std::vector<std::unique_ptr<Node>> children; + +  void AppendChild(std::unique_ptr<Node> child); +  void InsertChild(size_t index, std::unique_ptr<Node> child);    Attribute* FindAttribute(const android::StringPiece& ns, const android::StringPiece& name);    const Attribute* FindAttribute(const android::StringPiece& ns,                                   const android::StringPiece& name) const; -  xml::Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name); -  xml::Element* FindChildWithAttribute(const android::StringPiece& ns, -                                       const android::StringPiece& name, -                                       const android::StringPiece& attr_ns, -                                       const android::StringPiece& attr_name, -                                       const android::StringPiece& attr_value); -  std::vector<xml::Element*> GetChildElements(); -  std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) override; +  Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name); +  Element* FindChildWithAttribute(const android::StringPiece& ns, const android::StringPiece& name, +                                  const android::StringPiece& attr_ns, +                                  const android::StringPiece& attr_name, +                                  const android::StringPiece& attr_value); +  std::vector<Element*> GetChildElements(); + +  // Due to overriding of subtypes not working with unique_ptr, define a convenience Clone method +  // that knows cloning an element returns an element. +  std::unique_ptr<Element> CloneElement(const ElementCloneFunc& el_cloner) const; + +  std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) const override; + +  void Accept(Visitor* visitor) override;  }; -/** - * A Text (CDATA) XML node. Can not have any children. - */ -class Text : public BaseNode<Text> { +// A Text (CDATA) XML node. Can not have any children. +class Text : public Node {   public:    std::string text; -  std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) override; +  std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) const override; + +  void Accept(Visitor* visitor) override;  }; -/** - * An XML resource with a source, name, and XML tree. - */ +// An XML resource with a source, name, and XML tree.  class XmlResource {   public:    ResourceFile file; @@ -146,99 +134,121 @@ class XmlResource {    // is destroyed.    StringPool string_pool; -  std::unique_ptr<xml::Node> root; +  std::unique_ptr<xml::Element> root;  }; -/** - * Inflates an XML DOM from a text stream, logging errors to the logger. - * Returns the root node on success, or nullptr on failure. - */ -std::unique_ptr<XmlResource> Inflate(std::istream* in, IDiagnostics* diag, const Source& source); +// Inflates an XML DOM from an InputStream, logging errors to the logger. +// Returns the root node on success, or nullptr on failure. +std::unique_ptr<XmlResource> Inflate(io::InputStream* in, IDiagnostics* diag, const Source& source); -/** - * Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger. - * Returns the root node on success, or nullptr on failure. - */ +// Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger. +// Returns the root node on success, or nullptr on failure.  std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnostics* diag,                                       const Source& source); -Element* FindRootElement(XmlResource* doc);  Element* FindRootElement(Node* node); -/** - * A visitor interface for the different XML Node subtypes. This will not - * traverse into - * children. Use Visitor for that. - */ -class RawVisitor { - public: -  virtual ~RawVisitor() = default; - -  virtual void Visit(Namespace* node) {} -  virtual void Visit(Element* node) {} -  virtual void Visit(Text* text) {} -}; - -/** - * Visitor whose default implementation visits the children nodes of any node. - */ -class Visitor : public RawVisitor { +// Visitor whose default implementation visits the children nodes of any node. +class Visitor {   public: -  using RawVisitor::Visit; +  virtual ~Visitor() = default; -  void Visit(Namespace* node) override { VisitChildren(node); } +  virtual void Visit(Element* el) { +    VisitChildren(el); +  } -  void Visit(Element* node) override { VisitChildren(node); } +  virtual void Visit(Text* text) { +  } -  void Visit(Text* text) override { VisitChildren(text); } + protected: +  Visitor() = default; -  void VisitChildren(Node* node) { -    for (auto& child : node->children) { +  void VisitChildren(Element* el) { +    for (auto& child : el->children) {        child->Accept(this);      }    } + +  virtual void BeforeVisitElement(Element* el) { +  } +  virtual void AfterVisitElement(Element* el) { +  } + + private: +  DISALLOW_COPY_AND_ASSIGN(Visitor); + +  friend class Element;  }; -/** - * An XML DOM visitor that will record the package name for a namespace prefix. - */ +// An XML DOM visitor that will record the package name for a namespace prefix.  class PackageAwareVisitor : public Visitor, public IPackageDeclStack {   public:    using Visitor::Visit; -  void Visit(Namespace* ns) override;    Maybe<ExtractedPackage> TransformPackageAlias(        const android::StringPiece& alias, const android::StringPiece& local_package) const override; + protected: +  PackageAwareVisitor() = default; + +  void BeforeVisitElement(Element* el) override; +  void AfterVisitElement(Element* el) override; +   private: +  DISALLOW_COPY_AND_ASSIGN(PackageAwareVisitor); +    struct PackageDecl {      std::string prefix;      ExtractedPackage package;    }; -  std::vector<PackageDecl> package_decls_; +  std::vector<std::vector<PackageDecl>> package_decls_;  }; -// Implementations +namespace internal { -template <typename Derived> -void BaseNode<Derived>::Accept(RawVisitor* visitor) { -  visitor->Visit(static_cast<Derived*>(this)); -} +// Base class that overrides the default behaviour and does not descend into child nodes. +class NodeCastBase : public Visitor { + public: +  void Visit(Element* el) override { +  } +  void Visit(Text* el) override { +  } + + protected: +  NodeCastBase() = default; + +  void BeforeVisitElement(Element* el) override { +  } +  void AfterVisitElement(Element* el) override { +  } + + private: +  DISALLOW_COPY_AND_ASSIGN(NodeCastBase); +};  template <typename T> -class NodeCastImpl : public RawVisitor { +class NodeCastImpl : public NodeCastBase {   public: -  using RawVisitor::Visit; +  using NodeCastBase::Visit; + +  NodeCastImpl() = default;    T* value = nullptr; -  void Visit(T* v) override { value = v; } +  void Visit(T* v) override { +    value = v; +  } + + private: +  DISALLOW_COPY_AND_ASSIGN(NodeCastImpl);  }; +}  // namespace internal +  template <typename T>  T* NodeCast(Node* node) { -  NodeCastImpl<T> visitor; +  internal::NodeCastImpl<T> visitor;    node->Accept(&visitor);    return visitor.value;  } diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp index f0122e8c617a..6ed2d616f782 100644 --- a/tools/aapt2/xml/XmlDom_test.cpp +++ b/tools/aapt2/xml/XmlDom_test.cpp @@ -16,23 +16,22 @@  #include "xml/XmlDom.h" -#include <sstream>  #include <string> +#include "io/StringInputStream.h"  #include "test/Test.h" +using ::aapt::io::StringInputStream;  using ::testing::Eq;  using ::testing::NotNull;  using ::testing::SizeIs; +using ::testing::StrEq;  namespace aapt { - -constexpr const char* kXmlPreamble = -    "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; +namespace xml {  TEST(XmlDomTest, Inflate) { -  std::stringstream in(kXmlPreamble); -  in << R"( +  std::string input = R"(<?xml version="1.0" encoding="utf-8"?>        <Layout xmlns:android="http://schemas.android.com/apk/res/android"            android:layout_width="match_parent"            android:layout_height="wrap_content"> @@ -41,26 +40,25 @@ TEST(XmlDomTest, Inflate) {              android:layout_height="wrap_content" />        </Layout>)"; -  const Source source("test.xml");    StdErrDiagnostics diag; -  std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, &diag, source); +  StringInputStream in(input); +  std::unique_ptr<XmlResource> doc = Inflate(&in, &diag, Source("test.xml"));    ASSERT_THAT(doc, NotNull()); -  xml::Namespace* ns = xml::NodeCast<xml::Namespace>(doc->root.get()); -  ASSERT_THAT(ns, NotNull()); -  EXPECT_THAT(ns->namespace_uri, Eq(xml::kSchemaAndroid)); -  EXPECT_THAT(ns->namespace_prefix, Eq("android")); +  Element* el = doc->root.get(); +  EXPECT_THAT(el->namespace_decls, SizeIs(1u)); +  EXPECT_THAT(el->namespace_decls[0].uri, StrEq(xml::kSchemaAndroid)); +  EXPECT_THAT(el->namespace_decls[0].prefix, StrEq("android"));  }  // Escaping is handled after parsing of the values for resource-specific values.  TEST(XmlDomTest, ForwardEscapes) { -  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"( +  std::unique_ptr<XmlResource> doc = test::BuildXmlDom(R"(        <element value="\?hello" pattern="\\d{5}">\\d{5}</element>)"); -  xml::Element* el = xml::FindRootElement(doc.get()); -  ASSERT_THAT(el, NotNull()); +  Element* el = doc->root.get(); -  xml::Attribute* attr = el->FindAttribute({}, "pattern"); +  Attribute* attr = el->FindAttribute({}, "pattern");    ASSERT_THAT(attr, NotNull());    EXPECT_THAT(attr->value, Eq("\\\\d{5}")); @@ -70,21 +68,54 @@ TEST(XmlDomTest, ForwardEscapes) {    ASSERT_THAT(el->children, SizeIs(1u)); -  xml::Text* text = xml::NodeCast<xml::Text>(el->children[0].get()); +  Text* text = xml::NodeCast<xml::Text>(el->children[0].get());    ASSERT_THAT(text, NotNull());    EXPECT_THAT(text->text, Eq("\\\\d{5}"));  }  TEST(XmlDomTest, XmlEscapeSequencesAreParsed) { -  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<element value=""" />)"); - -  xml::Element* el = xml::FindRootElement(doc.get()); -  ASSERT_THAT(el, NotNull()); - -  xml::Attribute* attr = el->FindAttribute({}, "value"); +  std::unique_ptr<XmlResource> doc = test::BuildXmlDom(R"(<element value=""" />)"); +  Attribute* attr = doc->root->FindAttribute({}, "value");    ASSERT_THAT(attr, NotNull()); -    EXPECT_THAT(attr->value, Eq("\""));  } +class TestVisitor : public PackageAwareVisitor { + public: +  using PackageAwareVisitor::Visit; + +  void Visit(Element* el) override { +    if (el->name == "View1") { +      EXPECT_THAT(TransformPackageAlias("one", "local"), +                  Eq(make_value(ExtractedPackage{"com.one", false}))); +    } else if (el->name == "View2") { +      EXPECT_THAT(TransformPackageAlias("one", "local"), +                  Eq(make_value(ExtractedPackage{"com.one", false}))); +      EXPECT_THAT(TransformPackageAlias("two", "local"), +                  Eq(make_value(ExtractedPackage{"com.two", false}))); +    } else if (el->name == "View3") { +      EXPECT_THAT(TransformPackageAlias("one", "local"), +                  Eq(make_value(ExtractedPackage{"com.one", false}))); +      EXPECT_THAT(TransformPackageAlias("two", "local"), +                  Eq(make_value(ExtractedPackage{"com.two", false}))); +      EXPECT_THAT(TransformPackageAlias("three", "local"), +                  Eq(make_value(ExtractedPackage{"com.three", false}))); +    } +  } +}; + +TEST(XmlDomTest, PackageAwareXmlVisitor) { +  std::unique_ptr<XmlResource> doc = test::BuildXmlDom(R"( +      <View1 xmlns:one="http://schemas.android.com/apk/res/com.one"> +        <View2 xmlns:two="http://schemas.android.com/apk/res/com.two"> +          <View3 xmlns:three="http://schemas.android.com/apk/res/com.three" /> +        </View2> +      </View1>)"); + +  Debug::DumpXml(doc.get()); +  TestVisitor visitor; +  doc->root->Accept(&visitor); +} + +}  // namespace xml  }  // namespace aapt diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp index c2a9c8283a6d..30bdc507303b 100644 --- a/tools/aapt2/xml/XmlPullParser.cpp +++ b/tools/aapt2/xml/XmlPullParser.cpp @@ -22,14 +22,15 @@  #include "xml/XmlPullParser.h"  #include "xml/XmlUtil.h" -using android::StringPiece; +using ::aapt::io::InputStream; +using ::android::StringPiece;  namespace aapt {  namespace xml {  constexpr char kXmlNamespaceSep = 1; -XmlPullParser::XmlPullParser(std::istream& in) : in_(in), empty_(), depth_(0) { +XmlPullParser::XmlPullParser(InputStream* in) : in_(in), empty_(), depth_(0) {    parser_ = XML_ParserCreateNS(nullptr, kXmlNamespaceSep);    XML_SetUserData(parser_, this);    XML_SetElementHandler(parser_, StartElementHandler, EndElementHandler); @@ -40,30 +41,35 @@ XmlPullParser::XmlPullParser(std::istream& in) : in_(in), empty_(), depth_(0) {    event_queue_.push(EventData{Event::kStartDocument, 0, depth_++});  } -XmlPullParser::~XmlPullParser() { XML_ParserFree(parser_); } +XmlPullParser::~XmlPullParser() { +  XML_ParserFree(parser_); +}  XmlPullParser::Event XmlPullParser::Next() {    const Event currentEvent = event(); -  if (currentEvent == Event::kBadDocument || -      currentEvent == Event::kEndDocument) { +  if (currentEvent == Event::kBadDocument || currentEvent == Event::kEndDocument) {      return currentEvent;    }    event_queue_.pop();    while (event_queue_.empty()) { -    in_.read(buffer_, sizeof(buffer_) / sizeof(*buffer_)); +    const char* buffer = nullptr; +    size_t buffer_size = 0; +    bool done = false; +    if (!in_->Next(reinterpret_cast<const void**>(&buffer), &buffer_size)) { +      if (in_->HadError()) { +        error_ = in_->GetError(); +        event_queue_.push(EventData{Event::kBadDocument}); +        break; +      } -    const bool done = in_.eof(); -    if (in_.bad() && !done) { -      error_ = strerror(errno); -      event_queue_.push(EventData{Event::kBadDocument}); -      continue; +      done = true;      } -    if (XML_Parse(parser_, buffer_, in_.gcount(), done) == XML_STATUS_ERROR) { +    if (XML_Parse(parser_, buffer, buffer_size, done) == XML_STATUS_ERROR) {        error_ = XML_ErrorString(XML_GetErrorCode(parser_));        event_queue_.push(EventData{Event::kBadDocument}); -      continue; +      break;      }      if (done) { diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h index cdeeefd13976..a00caa139061 100644 --- a/tools/aapt2/xml/XmlPullParser.h +++ b/tools/aapt2/xml/XmlPullParser.h @@ -31,6 +31,7 @@  #include "androidfw/StringPiece.h"  #include "Resource.h" +#include "io/Io.h"  #include "process/IResourceTableConsumer.h"  #include "util/Maybe.h"  #include "xml/XmlUtil.h" @@ -64,7 +65,7 @@ class XmlPullParser : public IPackageDeclStack {    static bool SkipCurrentElement(XmlPullParser* parser);    static bool IsGoodEvent(Event event); -  explicit XmlPullParser(std::istream& in); +  explicit XmlPullParser(io::InputStream* in);    ~XmlPullParser();    /** @@ -169,9 +170,8 @@ class XmlPullParser : public IPackageDeclStack {      std::vector<Attribute> attributes;    }; -  std::istream& in_; +  io::InputStream* in_;    XML_Parser parser_; -  char buffer_[16384];    std::queue<EventData> event_queue_;    std::string error_;    const std::string empty_; @@ -228,18 +228,15 @@ inline ::std::ostream& operator<<(::std::ostream& out,    return out;  } -inline bool XmlPullParser::NextChildNode(XmlPullParser* parser, -                                         size_t start_depth) { +inline bool XmlPullParser::NextChildNode(XmlPullParser* parser, size_t start_depth) {    Event event;    // First get back to the start depth. -  while (IsGoodEvent(event = parser->Next()) && -         parser->depth() > start_depth + 1) { +  while (IsGoodEvent(event = parser->Next()) && parser->depth() > start_depth + 1) {    }    // Now look for the first good node. -  while ((event != Event::kEndElement || parser->depth() > start_depth) && -         IsGoodEvent(event)) { +  while ((event != Event::kEndElement || parser->depth() > start_depth) && IsGoodEvent(event)) {      switch (event) {        case Event::kText:        case Event::kComment: diff --git a/tools/aapt2/xml/XmlPullParser_test.cpp b/tools/aapt2/xml/XmlPullParser_test.cpp index 1cce4850cac5..681d9d48173f 100644 --- a/tools/aapt2/xml/XmlPullParser_test.cpp +++ b/tools/aapt2/xml/XmlPullParser_test.cpp @@ -16,21 +16,22 @@  #include "xml/XmlPullParser.h" -#include <sstream> -  #include "androidfw/StringPiece.h" +#include "io/StringInputStream.h"  #include "test/Test.h" -using android::StringPiece; +using ::aapt::io::StringInputStream; +using ::android::StringPiece;  namespace aapt {  TEST(XmlPullParserTest, NextChildNodeTraversesCorrectly) { -  std::stringstream str; -  str << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" -         "<a><b><c xmlns:a=\"http://schema.org\"><d/></c><e/></b></a>"; -  xml::XmlPullParser parser(str); +  std::string str = +      R"(<?xml version="1.0" encoding="utf-8"?> +         <a><b><c xmlns:a="http://schema.org"><d/></c><e/></b></a>)"; +  StringInputStream input(str); +  xml::XmlPullParser parser(&input);    const size_t depth_outer = parser.depth();    ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_outer)); diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h index 1650ac2124ac..866b6dcd7a88 100644 --- a/tools/aapt2/xml/XmlUtil.h +++ b/tools/aapt2/xml/XmlUtil.h @@ -26,79 +26,56 @@ namespace aapt {  namespace xml {  constexpr const char* kSchemaAuto = "http://schemas.android.com/apk/res-auto"; -constexpr const char* kSchemaPublicPrefix = -    "http://schemas.android.com/apk/res/"; -constexpr const char* kSchemaPrivatePrefix = -    "http://schemas.android.com/apk/prv/res/"; -constexpr const char* kSchemaAndroid = -    "http://schemas.android.com/apk/res/android"; +constexpr const char* kSchemaPublicPrefix = "http://schemas.android.com/apk/res/"; +constexpr const char* kSchemaPrivatePrefix = "http://schemas.android.com/apk/prv/res/"; +constexpr const char* kSchemaAndroid = "http://schemas.android.com/apk/res/android";  constexpr const char* kSchemaTools = "http://schemas.android.com/tools";  constexpr const char* kSchemaAapt = "http://schemas.android.com/aapt"; -/** - * Result of extracting a package name from a namespace URI declaration. - */ +// Result of extracting a package name from a namespace URI declaration.  struct ExtractedPackage { -  /** -   * The name of the package. This can be the empty string, which means that the -   * package -   * should be assumed to be the package being compiled. -   */ +  // The name of the package. This can be the empty string, which means that the package +  // should be assumed to be the package being compiled.    std::string package; -  /** -   * True if the package's private namespace was declared. This means that -   * private resources -   * are made visible. -   */ +  // True if the package's private namespace was declared. This means that private resources +  // are made visible.    bool private_namespace; + +  friend inline bool operator==(const ExtractedPackage& a, const ExtractedPackage& b) { +    return a.package == b.package && a.private_namespace == b.private_namespace; +  }  }; -/** - * Returns an ExtractedPackage struct if the namespace URI is of the form: - * http://schemas.android.com/apk/res/<package> or - * http://schemas.android.com/apk/prv/res/<package> - * - * Special case: if namespaceUri is http://schemas.android.com/apk/res-auto, - * returns an empty package name. - */ -Maybe<ExtractedPackage> ExtractPackageFromNamespace( -    const std::string& namespace_uri); +// Returns an ExtractedPackage struct if the namespace URI is of the form: +//   http://schemas.android.com/apk/res/<package> or +//   http://schemas.android.com/apk/prv/res/<package> +// +// Special case: if namespaceUri is http://schemas.android.com/apk/res-auto, +// returns an empty package name. +Maybe<ExtractedPackage> ExtractPackageFromNamespace(const std::string& namespace_uri); -/** - * Returns an XML Android namespace for the given package of the form: - * - * http://schemas.android.com/apk/res/<package> - * - * If privateReference == true, the package will be of the form: - * - * http://schemas.android.com/apk/prv/res/<package> - */ +// Returns an XML Android namespace for the given package of the form: +//   http://schemas.android.com/apk/res/<package> +// +// If privateReference == true, the package will be of the form: +//   http://schemas.android.com/apk/prv/res/<package>  std::string BuildPackageNamespace(const android::StringPiece& package,                                    bool private_reference = false); -/** - * Interface representing a stack of XML namespace declarations. When looking up - * the package - * for a namespace prefix, the stack is checked from top to bottom. - */ +// Interface representing a stack of XML namespace declarations. When looking up the package +// for a namespace prefix, the stack is checked from top to bottom.  struct IPackageDeclStack {    virtual ~IPackageDeclStack() = default; -  /** -   * Returns an ExtractedPackage struct if the alias given corresponds with a -   * package declaration. -   */ +  // Returns an ExtractedPackage struct if the alias given corresponds with a package declaration.    virtual Maybe<ExtractedPackage> TransformPackageAlias(        const android::StringPiece& alias, const android::StringPiece& local_package) const = 0;  }; -/** - * Helper function for transforming the original Reference inRef to a fully - * qualified reference - * via the IPackageDeclStack. This will also mark the Reference as private if - * the namespace of the package declaration was private. - */ +// Helper function for transforming the original Reference inRef to a fully qualified reference +// via the IPackageDeclStack. This will also mark the Reference as private if the namespace of the +// package declaration was private.  void TransformReferenceFromNamespace(IPackageDeclStack* decl_stack,                                       const android::StringPiece& local_package, Reference* in_ref); |