content: /*
   * Copyright (C) 2018 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
   * except in compliance with the License. You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software distributed under the
   * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied. See the License for the specific language governing
   * permissions and limitations under the License.
   */
  
  package com.android.server.slice;
  
  import android.annotation.NonNull;
  import android.util.ArrayMap;
  import android.util.ArraySet;
  import android.util.Slog;
  
  import com.android.server.slice.DirtyTracker.Persistable;
  import com.android.server.slice.SlicePermissionManager.PkgUser;
  
  import org.xmlpull.v1.XmlPullParser;
  import org.xmlpull.v1.XmlPullParserException;
  import org.xmlpull.v1.XmlSerializer;
  
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Objects;
  
  public class SliceProviderPermissions implements DirtyTracker, Persistable {
  
      private static final String TAG = "SliceProviderPermissions";
  
      static final String TAG_PROVIDER = "provider";
      private static final String TAG_AUTHORITY = "authority";
      private static final String TAG_PKG = "pkg";
      private static final String NAMESPACE = null;
  
      private static final String ATTR_PKG = "pkg";
      private static final String ATTR_AUTHORITY = "authority";
  
      private final PkgUser mPkg;
      private final ArrayMap<String, SliceAuthority> mAuths = new ArrayMap<>();
      private final DirtyTracker mTracker;
  
      public SliceProviderPermissions(@NonNull PkgUser pkg, @NonNull DirtyTracker tracker) {
          mPkg = pkg;
          mTracker = tracker;
      }
  
      public PkgUser getPkg() {
          return mPkg;
      }
  
      public synchronized Collection<SliceAuthority> getAuthorities() {
          return new ArrayList<>(mAuths.values());
      }
  
      public synchronized SliceAuthority getOrCreateAuthority(String authority) {
          SliceAuthority ret = mAuths.get(authority);
          if (ret == null) {
              ret = new SliceAuthority(authority, this);
              mAuths.put(authority, ret);
              onPersistableDirty(ret);
          }
          return ret;
      }
  
      @Override
      public void onPersistableDirty(Persistable obj) {
          mTracker.onPersistableDirty(this);
      }
  
      @Override
      public String getFileName() {
          return getFileName(mPkg);
      }
  
      public synchronized void writeTo(XmlSerializer out) throws IOException {
          out.startTag(NAMESPACE, TAG_PROVIDER);
          out.attribute(NAMESPACE, ATTR_PKG, mPkg.toString());
  
          final int N = mAuths.size();
          for (int i = 0; i < N; i++) {
              out.startTag(NAMESPACE, TAG_AUTHORITY);
              out.attribute(NAMESPACE, ATTR_AUTHORITY, mAuths.valueAt(i).mAuthority);
  
              mAuths.valueAt(i).writeTo(out);
  
              out.endTag(NAMESPACE, TAG_AUTHORITY);
          }
  
          out.endTag(NAMESPACE, TAG_PROVIDER);
      }
  
      public static SliceProviderPermissions createFrom(XmlPullParser parser, DirtyTracker tracker)
              throws XmlPullParserException, IOException {
          // Get to the beginning of the provider.
          while (parser.getEventType() != XmlPullParser.START_TAG
                  || !TAG_PROVIDER.equals(parser.getName())) {
              parser.next();
          }
          int depth = parser.getDepth();
          PkgUser pkgUser = new PkgUser(parser.getAttributeValue(NAMESPACE, ATTR_PKG));
          SliceProviderPermissions provider = new SliceProviderPermissions(pkgUser, tracker);
          parser.next();
  
          while (parser.getDepth() > depth) {
              if (parser.getEventType() == XmlPullParser.START_TAG
                      && TAG_AUTHORITY.equals(parser.getName())) {
                  try {
                      SliceAuthority authority = new SliceAuthority(
                              parser.getAttributeValue(NAMESPACE, ATTR_AUTHORITY), provider);
                      authority.readFrom(parser);
                      provider.mAuths.put(authority.getAuthority(), authority);
                  } catch (IllegalArgumentException e) {
                      Slog.e(TAG, "Couldn't read PkgUser", e);
                  }
              }
  
              parser.next();
          }
          return provider;
      }
  
      public static String getFileName(PkgUser pkg) {
          return String.format("provider_%s", pkg.toString());
      }
  
      public static class SliceAuthority implements Persistable {
          private final String mAuthority;
          private final DirtyTracker mTracker;
          private final ArraySet<PkgUser> mPkgs = new ArraySet<>();
  
          public SliceAuthority(String authority, DirtyTracker tracker) {
              mAuthority = authority;
              mTracker = tracker;
          }
  
          public String getAuthority() {
              return mAuthority;
          }
  
          public synchronized void addPkg(PkgUser pkg) {
              if (mPkgs.add(pkg)) {
                  mTracker.onPersistableDirty(this);
              }
          }
  
          public synchronized void removePkg(PkgUser pkg) {
              if (mPkgs.remove(pkg)) {
                  mTracker.onPersistableDirty(this);
              }
          }
  
          public synchronized Collection<PkgUser> getPkgs() {
              return new ArraySet<>(mPkgs);
          }
  
          @Override
          public String getFileName() {
              return null;
          }
  
          public synchronized void writeTo(XmlSerializer out) throws IOException {
              final int N = mPkgs.size();
              for (int i = 0; i < N; i++) {
                  out.startTag(NAMESPACE, TAG_PKG);
                  out.text(mPkgs.valueAt(i).toString());
                  out.endTag(NAMESPACE, TAG_PKG);
              }
          }
  
          public synchronized void readFrom(XmlPullParser parser)
                  throws IOException, XmlPullParserException {
              parser.next();
              int depth = parser.getDepth();
              while (parser.getDepth() >= depth) {
                  if (parser.getEventType() == XmlPullParser.START_TAG
                          && TAG_PKG.equals(parser.getName())) {
                      mPkgs.add(new PkgUser(parser.nextText()));
                  }
                  parser.next();
              }
          }
  
          @Override
          public boolean equals(Object obj) {
              if (!getClass().equals(obj != null ? obj.getClass() : null)) return false;
              SliceAuthority other = (SliceAuthority) obj;
              return Objects.equals(mAuthority, other.mAuthority)
                      && Objects.equals(mPkgs, other.mPkgs);
          }
  
          @Override
          public String toString() {
              return String.format("(%s: %s)", mAuthority, mPkgs.toString());
          }
      }
  }
  