Merge "Show "Call Blocking disabled" notification always when emergency call"
diff --git a/Android.bp b/Android.bp
index 678b5da..ac145fc7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -506,11 +506,14 @@
         "telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl",
         "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl",
         "telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl",
+        "telephony/java/android/telephony/mbms/IMbmsGroupCallSessionCallback.aidl",
         "telephony/java/android/telephony/mbms/IDownloadStatusListener.aidl",
         "telephony/java/android/telephony/mbms/IDownloadProgressListener.aidl",
         "telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl",
+        "telephony/java/android/telephony/mbms/IGroupCallCallback.aidl",
         "telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl",
         "telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl",
+        "telephony/java/android/telephony/mbms/vendor/IMbmsGroupCallService.aidl",
         "telephony/java/android/telephony/INetworkService.aidl",
         "telephony/java/android/telephony/INetworkServiceCallback.aidl",
         "telephony/java/com/android/ims/internal/IImsCallSession.aidl",
diff --git a/api/current.txt b/api/current.txt
index 68cb09f..4880101 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -42251,6 +42251,13 @@
     field public static final int STATUS_UNKNOWN = 0; // 0x0
   }
 
+  public class MbmsGroupCallSession implements java.lang.AutoCloseable {
+    method public void close();
+    method public static android.telephony.MbmsGroupCallSession create(android.content.Context, java.util.concurrent.Executor, int, android.telephony.mbms.MbmsGroupCallSessionCallback);
+    method public static android.telephony.MbmsGroupCallSession create(android.content.Context, java.util.concurrent.Executor, android.telephony.mbms.MbmsGroupCallSessionCallback);
+    method public android.telephony.mbms.GroupCall startGroupCall(java.util.concurrent.Executor, long, int[], int[], android.telephony.mbms.GroupCallCallback);
+  }
+
   public class MbmsStreamingSession implements java.lang.AutoCloseable {
     method public void close();
     method public static android.telephony.MbmsStreamingSession create(android.content.Context, java.util.concurrent.Executor, int, android.telephony.mbms.MbmsStreamingSessionCallback);
@@ -43196,6 +43203,29 @@
     field public static final android.os.Parcelable.Creator<android.telephony.mbms.FileServiceInfo> CREATOR;
   }
 
+  public class GroupCall implements java.lang.AutoCloseable {
+    method public void close();
+    method public long getTmgi();
+    method public void updateGroupCall(int[], int[]);
+    field public static final int REASON_BY_USER_REQUEST = 1; // 0x1
+    field public static final int REASON_FREQUENCY_CONFLICT = 3; // 0x3
+    field public static final int REASON_LEFT_MBMS_BROADCAST_AREA = 6; // 0x6
+    field public static final int REASON_NONE = 0; // 0x0
+    field public static final int REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE = 5; // 0x5
+    field public static final int REASON_OUT_OF_MEMORY = 4; // 0x4
+    field public static final int STATE_STALLED = 3; // 0x3
+    field public static final int STATE_STARTED = 2; // 0x2
+    field public static final int STATE_STOPPED = 1; // 0x1
+  }
+
+  public class GroupCallCallback {
+    ctor public GroupCallCallback();
+    method public void onBroadcastSignalStrengthUpdated(int);
+    method public void onError(int, java.lang.String);
+    method public void onGroupCallStateChanged(int, int);
+    field public static final int SIGNAL_STRENGTH_UNAVAILABLE = -1; // 0xffffffff
+  }
+
   public class MbmsDownloadReceiver extends android.content.BroadcastReceiver {
     ctor public MbmsDownloadReceiver();
     method public void onReceive(android.content.Context, android.content.Intent);
@@ -43244,6 +43274,14 @@
     field public static final int ERROR_UNABLE_TO_START_SERVICE = 302; // 0x12e
   }
 
+  public class MbmsGroupCallSessionCallback {
+    ctor public MbmsGroupCallSessionCallback();
+    method public void onAvailableSaisUpdated(java.util.List<java.lang.Integer>, java.util.List<java.util.List<java.lang.Integer>>);
+    method public void onError(int, java.lang.String);
+    method public void onMiddlewareReady();
+    method public void onServiceInterfaceAvailable(java.lang.String, int);
+  }
+
   public class MbmsStreamingSessionCallback {
     ctor public MbmsStreamingSessionCallback();
     method public void onError(int, java.lang.String);
diff --git a/api/system-current.txt b/api/system-current.txt
index d4ad8fd..a8c5d99 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5075,6 +5075,10 @@
     field public static final java.lang.String MBMS_DOWNLOAD_SERVICE_ACTION = "android.telephony.action.EmbmsDownload";
   }
 
+  public class MbmsGroupCallSession implements java.lang.AutoCloseable {
+    field public static final java.lang.String MBMS_GROUP_CALL_SERVICE_ACTION = "android.telephony.action.EmbmsGroupCall";
+  }
+
   public class MbmsStreamingSession implements java.lang.AutoCloseable {
     field public static final java.lang.String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming";
   }
@@ -5252,6 +5256,7 @@
     method public deprecated boolean getDataEnabled();
     method public deprecated boolean getDataEnabled(int);
     method public boolean getEmergencyCallbackMode();
+    method public java.lang.String getIsimDomain();
     method public int getSimApplicationState();
     method public int getSimCardState();
     method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
@@ -6375,6 +6380,17 @@
     method public int setTempFileRootDirectory(int, java.lang.String) throws android.os.RemoteException;
   }
 
+  public class MbmsGroupCallServiceBase extends android.app.Service {
+    ctor public MbmsGroupCallServiceBase();
+    method public void dispose(int) throws android.os.RemoteException;
+    method public int initialize(android.telephony.mbms.MbmsGroupCallSessionCallback, int) throws android.os.RemoteException;
+    method public void onAppCallbackDied(int, int);
+    method public android.os.IBinder onBind(android.content.Intent);
+    method public int startGroupCall(int, long, int[], int[], android.telephony.mbms.GroupCallCallback);
+    method public void stopGroupCall(int, long);
+    method public void updateGroupCall(int, long, int[], int[]);
+  }
+
   public class MbmsStreamingServiceBase extends android.os.Binder {
     ctor public MbmsStreamingServiceBase();
     method public void dispose(int) throws android.os.RemoteException;
diff --git a/api/test-current.txt b/api/test-current.txt
index 634293a..a7bb467 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -966,6 +966,10 @@
     field public static final java.lang.String MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA = "mbms-download-service-override";
   }
 
+  public class MbmsGroupCallSession implements java.lang.AutoCloseable {
+    field public static final java.lang.String MBMS_GROUP_CALL_SERVICE_OVERRIDE_METADATA = "mbms-group-call-service-override";
+  }
+
   public class MbmsStreamingSession implements java.lang.AutoCloseable {
     field public static final java.lang.String MBMS_STREAMING_SERVICE_OVERRIDE_METADATA = "mbms-streaming-service-override";
   }
@@ -1035,6 +1039,17 @@
     method public int setTempFileRootDirectory(int, java.lang.String) throws android.os.RemoteException;
   }
 
+  public class MbmsGroupCallServiceBase extends android.app.Service {
+    ctor public MbmsGroupCallServiceBase();
+    method public void dispose(int) throws android.os.RemoteException;
+    method public int initialize(android.telephony.mbms.MbmsGroupCallSessionCallback, int) throws android.os.RemoteException;
+    method public void onAppCallbackDied(int, int);
+    method public android.os.IBinder onBind(android.content.Intent);
+    method public int startGroupCall(int, long, int[], int[], android.telephony.mbms.GroupCallCallback);
+    method public void stopGroupCall(int, long);
+    method public void updateGroupCall(int, long, int[], int[]);
+  }
+
   public class MbmsStreamingServiceBase extends android.os.Binder {
     ctor public MbmsStreamingServiceBase();
     method public void dispose(int) throws android.os.RemoteException;
diff --git a/core/java/android/security/net/config/DirectoryCertificateSource.java b/core/java/android/security/net/config/DirectoryCertificateSource.java
index 119f5d0..4f4d62a 100644
--- a/core/java/android/security/net/config/DirectoryCertificateSource.java
+++ b/core/java/android/security/net/config/DirectoryCertificateSource.java
@@ -16,26 +16,23 @@
 
 package android.security.net.config;
 
-import android.os.Environment;
-import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.Log;
-import android.util.Pair;
+
+import libcore.io.IoUtils;
+
 import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.InputStream;
 import java.io.IOException;
-import java.security.cert.Certificate;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.util.Collections;
 import java.util.Set;
-import libcore.io.IoUtils;
-
-import com.android.org.conscrypt.Hex;
-import com.android.org.conscrypt.NativeCrypto;
 
 import javax.security.auth.x500.X500Principal;
 
@@ -192,8 +189,36 @@
     }
 
     private String getHash(X500Principal name) {
-        int hash = NativeCrypto.X509_NAME_hash_old(name);
-        return Hex.intToHexString(hash, 8);
+        int hash = hashName(name);
+        return intToHexString(hash, 8);
+    }
+
+    private static final char[] DIGITS = {
+            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+    private static String intToHexString(int i, int minWidth) {
+        int bufLen = 8;  // Max number of hex digits in an int
+        char[] buf = new char[bufLen];
+        int cursor = bufLen;
+
+        do {
+            buf[--cursor] = DIGITS[i & 0xf];
+        } while ((i >>>= 4) != 0 || (bufLen - cursor < minWidth));
+
+        return new String(buf, cursor, bufLen - cursor);
+    }
+
+    // This code matches the code in X509_NAME_hash_old() in Conscrypt, which in turn matches
+    // the names of certificate files.  It must be kept in sync.
+    private static int hashName(X500Principal principal) {
+        try {
+            byte[] digest = MessageDigest.getInstance("MD5").digest(principal.getEncoded());
+            int offset = 0;
+            return (((digest[offset++] & 0xff) << 0) | ((digest[offset++] & 0xff) << 8)
+                    | ((digest[offset++] & 0xff) << 16) | ((digest[offset] & 0xff) << 24));
+        } catch (NoSuchAlgorithmException e) {
+            throw new AssertionError(e);
+        }
     }
 
     private X509Certificate readCertificate(String file) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 271d205..fae4db9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -17529,7 +17529,8 @@
         // Prepare the application profiles for the new code paths.
         // This needs to be done before invoking dexopt so that any install-time profile
         // can be used for optimizations.
-        mArtManagerService.prepareAppProfiles(pkg, resolveUserIds(args.user.getIdentifier()));
+        mArtManagerService.prepareAppProfiles(pkg, resolveUserIds(args.user.getIdentifier()),
+                /* updateReferenceProfileContent= */ true);
 
         // Check whether we need to dexopt the app.
         //
@@ -22606,8 +22607,18 @@
         //
         // We also have to cover non system users because we do not call the usual install package
         // methods for them.
+        //
+        // NOTE: in order to speed up first boot time we only create the current profile and do not
+        // update the content of the reference profile. A system image should already be configured
+        // with the right profile keys and the profiles for the speed-profile prebuilds should
+        // already be copied. That's done in #performDexOptUpgrade.
+        //
+        // TODO(calin, mathieuc): We should use .dm files for prebuilds profiles instead of
+        // manually copying them in #performDexOptUpgrade. When we do that we should have a more
+        // granular check here and only update the existing profiles.
         if (mIsUpgrade || mFirstBoot || (userId != UserHandle.USER_SYSTEM)) {
-            mArtManagerService.prepareAppProfiles(pkg, userId);
+            mArtManagerService.prepareAppProfiles(pkg, userId,
+                /* updateReferenceProfileContent= */ false);
         }
 
         if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) {
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 0ba7822..833cc5b 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -389,7 +389,8 @@
      *   - create the current primary profile to save time at app startup time.
      *   - copy the profiles from the associated dex metadata file to the reference profile.
      */
-    public void prepareAppProfiles(PackageParser.Package pkg, @UserIdInt int user) {
+    public void prepareAppProfiles(PackageParser.Package pkg, @UserIdInt int user,
+            boolean updateReferenceProfileContent) {
         final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
         if (user < 0) {
             Slog.wtf(TAG, "Invalid user id: " + user);
@@ -404,8 +405,14 @@
             for (int i = codePathsProfileNames.size() - 1; i >= 0; i--) {
                 String codePath = codePathsProfileNames.keyAt(i);
                 String profileName = codePathsProfileNames.valueAt(i);
-                File dexMetadata = DexMetadataHelper.findDexMetadataForFile(new File(codePath));
-                String dexMetadataPath = dexMetadata == null ? null : dexMetadata.getAbsolutePath();
+                String dexMetadataPath = null;
+                // Passing the dex metadata file to the prepare method will update the reference
+                // profile content. As such, we look for the dex metadata file only if we need to
+                // perform an update.
+                if (updateReferenceProfileContent) {
+                    File dexMetadata = DexMetadataHelper.findDexMetadataForFile(new File(codePath));
+                    dexMetadataPath = dexMetadata == null ? null : dexMetadata.getAbsolutePath();
+                }
                 synchronized (mInstaller) {
                     boolean result = mInstaller.prepareAppProfile(pkg.packageName, user, appId,
                             profileName, codePath, dexMetadataPath);
@@ -423,9 +430,10 @@
     /**
      * Prepares the app profiles for a set of users. {@see ArtManagerService#prepareAppProfiles}.
      */
-    public void prepareAppProfiles(PackageParser.Package pkg, int[] user) {
+    public void prepareAppProfiles(PackageParser.Package pkg, int[] user,
+            boolean updateReferenceProfileContent) {
         for (int i = 0; i < user.length; i++) {
-            prepareAppProfiles(pkg, user[i]);
+            prepareAppProfiles(pkg, user[i], updateReferenceProfileContent);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
index e3e1590..602ce3b 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
@@ -93,7 +93,7 @@
     @GuardedBy("mPackageUseInfoMap")
     private final Map<String, PackageUseInfo> mPackageUseInfoMap;
 
-    public PackageDexUsage() {
+    /* package */ PackageDexUsage() {
         super("package-dex-usage.list", "PackageDexUsage_DiskWriter", /*lock*/ false);
         mPackageUseInfoMap = new HashMap<>();
     }
@@ -116,7 +116,7 @@
      * @return true if the dex load constitutes new information, or false if this information
      *         has been seen before.
      */
-    public boolean record(String owningPackageName, String dexPath, int ownerUserId,
+    /* package */ boolean record(String owningPackageName, String dexPath, int ownerUserId,
             String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit,
             String loadingPackageName, String classLoaderContext) {
         if (!PackageManagerServiceUtils.checkISA(loaderIsa)) {
@@ -193,7 +193,7 @@
      * Convenience method for sync reads which does not force the user to pass a useless
      * (Void) null.
      */
-    public void read() {
+    /* package */ void read() {
       read((Void) null);
     }
 
@@ -558,7 +558,7 @@
      * Remove the usage data associated with package {@code packageName}.
      * @return true if the package usage was found and removed successfully.
      */
-    public boolean removePackage(String packageName) {
+    /* package */ boolean removePackage(String packageName) {
         synchronized (mPackageUseInfoMap) {
             return mPackageUseInfoMap.remove(packageName) != null;
         }
@@ -653,11 +653,12 @@
         return packages;
     }
 
-    public void clear() {
+    /* package */ void clear() {
         synchronized (mPackageUseInfoMap) {
             mPackageUseInfoMap.clear();
         }
     }
+
     // Creates a deep copy of the class' mPackageUseInfoMap.
     private Map<String, PackageUseInfo> clonePackageUseInfoMap() {
         Map<String, PackageUseInfo> clone = new HashMap<>();
@@ -679,7 +680,7 @@
         throw new IllegalArgumentException("Unknown bool encoding: " + bool);
     }
 
-    public String dump() {
+    /* package */ String dump() {
         StringWriter sw = new StringWriter();
         write(sw);
         return sw.toString();
diff --git a/telephony/java/android/telephony/MbmsGroupCallSession.java b/telephony/java/android/telephony/MbmsGroupCallSession.java
new file mode 100644
index 0000000..e373797
--- /dev/null
+++ b/telephony/java/android/telephony/MbmsGroupCallSession.java
@@ -0,0 +1,300 @@
+/*
+ * 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 android.telephony;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.telephony.mbms.GroupCall;
+import android.telephony.mbms.GroupCallCallback;
+import android.telephony.mbms.InternalGroupCallCallback;
+import android.telephony.mbms.InternalGroupCallSessionCallback;
+import android.telephony.mbms.MbmsErrors;
+import android.telephony.mbms.MbmsGroupCallSessionCallback;
+import android.telephony.mbms.MbmsUtils;
+import android.telephony.mbms.vendor.IMbmsGroupCallService;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * This class provides functionality for accessing group call functionality over MBMS.
+ */
+public class MbmsGroupCallSession implements AutoCloseable {
+    private static final String LOG_TAG = "MbmsGroupCallSession";
+
+    /**
+     * Service action which must be handled by the middleware implementing the MBMS group call
+     * interface.
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String MBMS_GROUP_CALL_SERVICE_ACTION =
+            "android.telephony.action.EmbmsGroupCall";
+
+    /**
+     * Metadata key that specifies the component name of the service to bind to for group calls.
+     * @hide
+     */
+    @TestApi
+    public static final String MBMS_GROUP_CALL_SERVICE_OVERRIDE_METADATA =
+            "mbms-group-call-service-override";
+
+    private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
+
+    private AtomicReference<IMbmsGroupCallService> mService = new AtomicReference<>(null);
+    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+        @Override
+        public void binderDied() {
+            sIsInitialized.set(false);
+            mInternalCallback.onError(MbmsErrors.ERROR_MIDDLEWARE_LOST,
+                    "Received death notification");
+        }
+    };
+
+    private InternalGroupCallSessionCallback mInternalCallback;
+    private Set<GroupCall> mKnownActiveGroupCalls = new ArraySet<>();
+
+    private final Context mContext;
+    private int mSubscriptionId;
+
+    /** @hide */
+    private MbmsGroupCallSession(Context context, Executor executor, int subscriptionId,
+            MbmsGroupCallSessionCallback callback) {
+        mContext = context;
+        mSubscriptionId = subscriptionId;
+        mInternalCallback = new InternalGroupCallSessionCallback(callback, executor);
+    }
+
+    /**
+     * Create a new {@link MbmsGroupCallSession} using the given subscription ID.
+     *
+     * You may only have one instance of {@link MbmsGroupCallSession} per UID. If you call this
+     * method while there is an active instance of {@link MbmsGroupCallSession} 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 MbmsGroupCallSessionCallback#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 {@link #close()} on the instance of
+     * {@link MbmsGroupCallSession} that you received before calling this method again.
+     *
+     * @param context The {@link Context} to use.
+     * @param executor The executor on which you wish to execute callbacks.
+     * @param subscriptionId The subscription ID to use.
+     * @param callback A callback object on which you wish to receive results of asynchronous
+     *                 operations.
+     * @return An instance of {@link MbmsGroupCallSession}, or null if an error occurred.
+     */
+    public static @Nullable MbmsGroupCallSession create(@NonNull Context context,
+            @NonNull Executor executor, int subscriptionId,
+            final @NonNull MbmsGroupCallSessionCallback callback) {
+        if (!sIsInitialized.compareAndSet(false, true)) {
+            throw new IllegalStateException("Cannot create two instances of MbmsGroupCallSession");
+        }
+        MbmsGroupCallSession session = new MbmsGroupCallSession(context, executor,
+                subscriptionId, callback);
+
+        final int result = session.bindAndInitialize();
+        if (result != MbmsErrors.SUCCESS) {
+            sIsInitialized.set(false);
+            executor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onError(result, null);
+                }
+            });
+            return null;
+        }
+        return session;
+    }
+
+    /**
+     * Create a new {@link MbmsGroupCallSession} using the system default data subscription ID.
+     * See {@link #create(Context, Executor, int, MbmsGroupCallSessionCallback)}.
+     */
+    public static MbmsGroupCallSession create(@NonNull Context context,
+            @NonNull Executor executor, @NonNull MbmsGroupCallSessionCallback callback) {
+        return create(context, executor, SubscriptionManager.getDefaultSubscriptionId(), callback);
+    }
+
+    /**
+     * Terminates this instance. Also terminates
+     * any group calls spawned from this instance as if
+     * {@link GroupCall#close()} 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 MbmsGroupCallSessionCallback}, but callbacks that have already been
+     * enqueued will still be delivered.
+     *
+     * It is safe to call {@link #create(Context, Executor, int, MbmsGroupCallSessionCallback)} to
+     * obtain another instance of {@link MbmsGroupCallSession} immediately after this method
+     * returns.
+     *
+     * May throw an {@link IllegalStateException}
+     */
+    public void close() {
+        try {
+            IMbmsGroupCallService groupCallService = mService.get();
+            if (groupCallService == null) {
+                // Ignore and return, assume already disposed.
+                return;
+            }
+            groupCallService.dispose(mSubscriptionId);
+            for (GroupCall s : mKnownActiveGroupCalls) {
+                s.getCallback().stop();
+            }
+            mKnownActiveGroupCalls.clear();
+        } catch (RemoteException e) {
+            // Ignore for now
+        } finally {
+            mService.set(null);
+            sIsInitialized.set(false);
+            mInternalCallback.stop();
+        }
+    }
+
+    /**
+     * Starts the requested group call, reporting status to the indicated callback.
+     * Returns an object used to control that call.
+     *
+     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
+     *
+     * Asynchronous errors through the callback include any of the errors in
+     * {@link MbmsErrors.GeneralErrors}.
+     *
+     * @param executor The executor on which you wish to execute callbacks for this stream.
+     * @param tmgi The TMGI, an identifier for the group call you want to join.
+     * @param saiArray An array of SAIs for the group call that should be negotiated separately with
+     *                the carrier.
+     * @param frequencyArray An array of frequencies for the group call that should be negotiated
+     *                separately with the carrier.
+     * @param callback The callback that you want to receive information about the call on.
+     * @return An instance of {@link GroupCall} through which the call can be controlled.
+     *         May be {@code null} if an error occurred.
+     */
+    public @Nullable GroupCall startGroupCall(@NonNull Executor executor, long tmgi, int[] saiArray,
+            int[] frequencyArray, @NonNull GroupCallCallback callback) {
+        IMbmsGroupCallService groupCallService = mService.get();
+        if (groupCallService == null) {
+            throw new IllegalStateException("Middleware not yet bound");
+        }
+
+        InternalGroupCallCallback serviceCallback = new InternalGroupCallCallback(
+                callback, executor);
+
+        GroupCall serviceForApp = new GroupCall(mSubscriptionId,
+                groupCallService, this, tmgi, serviceCallback);
+        mKnownActiveGroupCalls.add(serviceForApp);
+
+        try {
+            int returnCode = groupCallService.startGroupCall(
+                    mSubscriptionId, tmgi, saiArray, frequencyArray, serviceCallback);
+            if (returnCode == MbmsErrors.UNKNOWN) {
+                // Unbind and throw an obvious error
+                close();
+                throw new IllegalStateException("Middleware must not return an unknown error code");
+            }
+            if (returnCode != MbmsErrors.SUCCESS) {
+                mInternalCallback.onError(returnCode, null);
+                return null;
+            }
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "Remote process died");
+            mService.set(null);
+            sIsInitialized.set(false);
+            mInternalCallback.onError(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+            return null;
+        }
+
+        return serviceForApp;
+    }
+
+    /** @hide */
+    public void onGroupCallStopped(GroupCall service) {
+        mKnownActiveGroupCalls.remove(service);
+    }
+
+    private int bindAndInitialize() {
+        return MbmsUtils.startBinding(mContext, MBMS_GROUP_CALL_SERVICE_ACTION,
+                new ServiceConnection() {
+                    @Override
+                    public void onServiceConnected(ComponentName name, IBinder service) {
+                        IMbmsGroupCallService groupCallService =
+                                IMbmsGroupCallService.Stub.asInterface(service);
+                        int result;
+                        try {
+                            result = groupCallService.initialize(mInternalCallback,
+                                    mSubscriptionId);
+                        } catch (RemoteException e) {
+                            Log.e(LOG_TAG, "Service died before initialization");
+                            mInternalCallback.onError(
+                                    MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
+                                    e.toString());
+                            sIsInitialized.set(false);
+                            return;
+                        } catch (RuntimeException e) {
+                            Log.e(LOG_TAG, "Runtime exception during initialization");
+                            mInternalCallback.onError(
+                                    MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
+                                    e.toString());
+                            sIsInitialized.set(false);
+                            return;
+                        }
+                        if (result == MbmsErrors.UNKNOWN) {
+                            // Unbind and throw an obvious error
+                            close();
+                            throw new IllegalStateException("Middleware must not return"
+                                    + " an unknown error code");
+                        }
+                        if (result != MbmsErrors.SUCCESS) {
+                            mInternalCallback.onError(result,
+                                    "Error returned during initialization");
+                            sIsInitialized.set(false);
+                            return;
+                        }
+                        try {
+                            groupCallService.asBinder().linkToDeath(mDeathRecipient, 0);
+                        } catch (RemoteException e) {
+                            mInternalCallback.onError(MbmsErrors.ERROR_MIDDLEWARE_LOST,
+                                    "Middleware lost during initialization");
+                            sIsInitialized.set(false);
+                            return;
+                        }
+                        mService.set(groupCallService);
+                    }
+
+                    @Override
+                    public void onServiceDisconnected(ComponentName name) {
+                        sIsInitialized.set(false);
+                        mService.set(null);
+                    }
+                });
+    }
+}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 7f27009..edddcd1 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4139,11 +4139,16 @@
     }
 
     /**
-     * Returns the IMS home network domain name that was loaded from the ISIM.
-     * @return the IMS domain name, or null if not present or not loaded
+     * Returns the IMS home network domain name that was loaded from the ISIM {@see #APPTYPE_ISIM}.
+     * @return the IMS domain name. Returns {@code null} if ISIM hasn't been loaded or IMS domain
+     * hasn't been loaded or isn't present on the ISIM.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public String getIsimDomain() {
         try {
             IPhoneSubInfo info = getSubscriberInfo();
diff --git a/telephony/java/android/telephony/mbms/GroupCall.java b/telephony/java/android/telephony/mbms/GroupCall.java
new file mode 100644
index 0000000..9aca18e
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/GroupCall.java
@@ -0,0 +1,176 @@
+/*
+ * 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 android.telephony.mbms;
+
+import android.annotation.IntDef;
+import android.os.RemoteException;
+import android.telephony.MbmsGroupCallSession;
+import android.telephony.mbms.vendor.IMbmsGroupCallService;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class used to represent a single MBMS group call. After a call has been started with
+ * {@link MbmsGroupCallSession#startGroupCall},
+ * this class is used to hold information about the call and control it.
+ */
+public class GroupCall implements AutoCloseable {
+    private static final String LOG_TAG = "MbmsGroupCall";
+
+    /**
+     * The state of a group call, reported via
+     * {@link GroupCallCallback#onGroupCallStateChanged(int, int)}
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "STATE_" }, value = {STATE_STOPPED, STATE_STARTED, STATE_STALLED})
+    public @interface GroupCallState {}
+    public static final int STATE_STOPPED = 1;
+    public static final int STATE_STARTED = 2;
+    public static final int STATE_STALLED = 3;
+
+    /**
+     * The reason for a call state change, reported via
+     * {@link GroupCallCallback#onGroupCallStateChanged(int, int)}
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "REASON_" },
+            value = {REASON_BY_USER_REQUEST, REASON_FREQUENCY_CONFLICT,
+                    REASON_OUT_OF_MEMORY, REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE,
+                    REASON_LEFT_MBMS_BROADCAST_AREA, REASON_NONE})
+    public @interface GroupCallStateChangeReason {}
+
+    /**
+     * Indicates that the middleware does not have a reason to provide for the state change.
+     */
+    public static final int REASON_NONE = 0;
+
+    /**
+     * State changed due to a call to {@link #close()} or
+     * {@link MbmsGroupCallSession#startGroupCall}
+     */
+    public static final int REASON_BY_USER_REQUEST = 1;
+
+    // 2 is unused to match up with streaming.
+
+    /**
+     * State changed due to a frequency conflict with another requested call.
+     */
+    public static final int REASON_FREQUENCY_CONFLICT = 3;
+
+    /**
+     * State changed due to the middleware running out of memory
+     */
+    public static final int REASON_OUT_OF_MEMORY = 4;
+
+    /**
+     * State changed due to the device leaving the home carrier's LTE network.
+     */
+    public static final int REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE = 5;
+
+    /**
+     * State changed due to the device leaving the area where this call is being broadcast.
+     */
+    public static final int REASON_LEFT_MBMS_BROADCAST_AREA = 6;
+
+    private final int mSubscriptionId;
+    private final long mTmgi;
+    private final MbmsGroupCallSession mParentSession;
+    private final InternalGroupCallCallback mCallback;
+    private IMbmsGroupCallService mService;
+
+    /**
+     * @hide
+     */
+    public GroupCall(int subscriptionId,
+            IMbmsGroupCallService service,
+            MbmsGroupCallSession session,
+            long tmgi,
+            InternalGroupCallCallback callback) {
+        mSubscriptionId = subscriptionId;
+        mParentSession = session;
+        mService = service;
+        mTmgi = tmgi;
+        mCallback = callback;
+    }
+
+    /**
+     * Retrieve the TMGI (Temporary Mobile Group Identity) corresponding to this call.
+     */
+    public long getTmgi() {
+        return mTmgi;
+    }
+
+    /**
+     * Send an update to the middleware when the SAI (Service Area Identifier) list and frequency
+     * information of the group call has * changed. Callers must obtain this information from the
+     * wireless carrier independently.
+     * @param saiArray New array of SAIs that the call is available on.
+     * @param frequencyArray New array of frequencies that the call is available on.
+     */
+    public void updateGroupCall(int[] saiArray, int[] frequencyArray) {
+        if (mService == null) {
+            throw new IllegalStateException("No group call service attached");
+        }
+
+        try {
+            mService.updateGroupCall(mSubscriptionId, mTmgi, saiArray, frequencyArray);
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "Remote process died");
+            mService = null;
+            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+        } finally {
+            mParentSession.onGroupCallStopped(this);
+        }
+    }
+
+    /**
+     * Stop this group call. Further operations on this object will fail with an
+     * {@link IllegalStateException}.
+     *
+     * May throw an {@link IllegalStateException}
+     */
+    @Override
+    public void close() {
+        if (mService == null) {
+            throw new IllegalStateException("No group call service attached");
+        }
+
+        try {
+            mService.stopGroupCall(mSubscriptionId, mTmgi);
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "Remote process died");
+            mService = null;
+            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+        } finally {
+            mParentSession.onGroupCallStopped(this);
+        }
+    }
+
+    /** @hide */
+    public InternalGroupCallCallback getCallback() {
+        return mCallback;
+    }
+
+    private void sendErrorToApp(int errorCode, String message) {
+        mCallback.onError(errorCode, message);
+    }
+}
+
diff --git a/telephony/java/android/telephony/mbms/GroupCallCallback.java b/telephony/java/android/telephony/mbms/GroupCallCallback.java
new file mode 100644
index 0000000..001bb02
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/GroupCallCallback.java
@@ -0,0 +1,87 @@
+/*
+ * 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 android.telephony.mbms;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A callback class for use when the application is in a group call. The middleware
+ * will provide updates on the status of the call via this callback.
+ */
+public class GroupCallCallback {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            MbmsErrors.ERROR_NO_UNIQUE_MIDDLEWARE,
+            MbmsErrors.ERROR_MIDDLEWARE_LOST,
+            MbmsErrors.ERROR_MIDDLEWARE_NOT_BOUND,
+            MbmsErrors.GeneralErrors.ERROR_MIDDLEWARE_NOT_YET_READY,
+            MbmsErrors.GeneralErrors.ERROR_OUT_OF_MEMORY,
+            MbmsErrors.GeneralErrors.ERROR_MIDDLEWARE_TEMPORARILY_UNAVAILABLE,
+            MbmsErrors.GeneralErrors.ERROR_IN_E911,
+            MbmsErrors.GeneralErrors.ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE,
+            MbmsErrors.GeneralErrors.ERROR_UNABLE_TO_READ_SIM,
+            MbmsErrors.GeneralErrors.ERROR_CARRIER_CHANGE_NOT_ALLOWED}, prefix = { "ERROR_" })
+    private @interface GroupCallError{}
+
+    /**
+     * Indicates broadcast signal strength is not available for this call.
+     *
+     * This may be due to the call no longer being available due to geography
+     * or timing (end of service)
+     */
+    public static final int SIGNAL_STRENGTH_UNAVAILABLE = -1;
+
+    /**
+     * Called by the middleware when it has detected an error condition in this group call. The
+     * 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(@GroupCallError int errorCode, @Nullable String message) {
+        // default implementation empty
+    }
+
+    /**
+     * Called to indicate this call has changed state.
+     *
+     * See {@link GroupCall#STATE_STOPPED}, {@link GroupCall#STATE_STARTED}
+     * and {@link GroupCall#STATE_STALLED}.
+     */
+    public void onGroupCallStateChanged(@GroupCall.GroupCallState int state,
+            @GroupCall.GroupCallStateChangeReason int reason) {
+        // default implementation empty
+    }
+
+    /**
+     * Broadcast Signal Strength updated.
+     *
+     * This signal strength is the BROADCAST signal strength which,
+     * depending on technology in play and it's deployment, may be
+     * stronger or weaker than the traditional UNICAST signal
+     * strength.  It a simple int from 0-4 for valid levels or
+     * {@link #SIGNAL_STRENGTH_UNAVAILABLE} if broadcast is not available
+     * for this call due to timing, geography or popularity.
+     */
+    public void onBroadcastSignalStrengthUpdated(int signalStrength) {
+        // default implementation empty
+    }
+}
diff --git a/telephony/java/android/telephony/mbms/IGroupCallCallback.aidl b/telephony/java/android/telephony/mbms/IGroupCallCallback.aidl
new file mode 100755
index 0000000..844b634
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/IGroupCallCallback.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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 android.telephony.mbms;
+
+/**
+ * @hide
+ */
+oneway interface IGroupCallCallback {
+    void onError(int errorCode, String message);
+    void onGroupCallStateChanged(int state, int reason);
+    void onBroadcastSignalStrengthUpdated(int signalStrength);
+}
diff --git a/telephony/java/android/telephony/mbms/IMbmsGroupCallSessionCallback.aidl b/telephony/java/android/telephony/mbms/IMbmsGroupCallSessionCallback.aidl
new file mode 100755
index 0000000..1a1c7f8
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/IMbmsGroupCallSessionCallback.aidl
@@ -0,0 +1,33 @@
+/*
+** Copyright 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 android.telephony.mbms;
+
+import java.util.List;
+
+/**
+ * @hide
+ */
+oneway interface IMbmsGroupCallSessionCallback
+{
+    void onError(int errorCode, String message);
+
+    void onAvailableSaisUpdated(in List currentSai, in List availableSais);
+
+    void onServiceInterfaceAvailable(String interfaceName, int index);
+
+    void onMiddlewareReady();
+}
diff --git a/telephony/java/android/telephony/mbms/InternalGroupCallCallback.java b/telephony/java/android/telephony/mbms/InternalGroupCallCallback.java
new file mode 100644
index 0000000..2910bb3
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/InternalGroupCallCallback.java
@@ -0,0 +1,96 @@
+/*
+ * 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 android.telephony.mbms;
+
+import android.os.Binder;
+
+import java.util.concurrent.Executor;
+
+/** @hide */
+public class InternalGroupCallCallback extends IGroupCallCallback.Stub {
+    private final GroupCallCallback mAppCallback;
+    private final Executor mExecutor;
+    private volatile boolean mIsStopped = false;
+
+    public InternalGroupCallCallback(GroupCallCallback appCallback,
+            Executor executor) {
+        mAppCallback = appCallback;
+        mExecutor = executor;
+    }
+
+    @Override
+    public void onError(final int errorCode, final String message) {
+        if (mIsStopped) {
+            return;
+        }
+
+        mExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    mAppCallback.onError(errorCode, message);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onGroupCallStateChanged(final int state, final int reason) {
+        if (mIsStopped) {
+            return;
+        }
+
+        mExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    mAppCallback.onGroupCallStateChanged(state, reason);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onBroadcastSignalStrengthUpdated(final int signalStrength) {
+        if (mIsStopped) {
+            return;
+        }
+
+        mExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    mAppCallback.onBroadcastSignalStrengthUpdated(signalStrength);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        });
+    }
+
+    /** Prevents this callback from calling the app */
+    public void stop() {
+        mIsStopped = true;
+    }
+}
diff --git a/telephony/java/android/telephony/mbms/InternalGroupCallSessionCallback.java b/telephony/java/android/telephony/mbms/InternalGroupCallSessionCallback.java
new file mode 100644
index 0000000..4c9cf4d
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/InternalGroupCallSessionCallback.java
@@ -0,0 +1,116 @@
+/*
+ * 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 android.telephony.mbms;
+
+import android.os.Binder;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/** @hide */
+public class InternalGroupCallSessionCallback extends IMbmsGroupCallSessionCallback.Stub {
+    private final Executor mExecutor;
+    private final MbmsGroupCallSessionCallback mAppCallback;
+    private volatile boolean mIsStopped = false;
+
+    public InternalGroupCallSessionCallback(MbmsGroupCallSessionCallback appCallback,
+            Executor executor) {
+        mAppCallback = appCallback;
+        mExecutor = executor;
+    }
+
+    @Override
+    public void onError(final int errorCode, final String message) {
+        if (mIsStopped) {
+            return;
+        }
+
+        mExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    mAppCallback.onError(errorCode, message);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onAvailableSaisUpdated(final List currentSais, final List availableSais) {
+        if (mIsStopped) {
+            return;
+        }
+
+        mExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    mAppCallback.onAvailableSaisUpdated(currentSais, availableSais);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onServiceInterfaceAvailable(final String interfaceName, final int index) {
+        if (mIsStopped) {
+            return;
+        }
+
+        mExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    mAppCallback.onServiceInterfaceAvailable(interfaceName, index);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onMiddlewareReady() {
+        if (mIsStopped) {
+            return;
+        }
+
+        mExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    mAppCallback.onMiddlewareReady();
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        });
+    }
+
+    /** Prevents this callback from calling the app */
+    public void stop() {
+        mIsStopped = true;
+    }
+}
diff --git a/telephony/java/android/telephony/mbms/MbmsGroupCallSessionCallback.java b/telephony/java/android/telephony/mbms/MbmsGroupCallSessionCallback.java
new file mode 100644
index 0000000..7da734e
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/MbmsGroupCallSessionCallback.java
@@ -0,0 +1,99 @@
+/*
+ * 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 android.telephony.mbms;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.telephony.MbmsGroupCallSession;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * A callback class that is used to receive information from the middleware on MBMS group-call
+ * services. An instance of this object should be passed into
+ * {@link MbmsGroupCallSession#create(Context, Executor, int, MbmsGroupCallSessionCallback)}.
+ */
+public class MbmsGroupCallSessionCallback {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            MbmsErrors.ERROR_NO_UNIQUE_MIDDLEWARE,
+            MbmsErrors.ERROR_MIDDLEWARE_LOST,
+            MbmsErrors.ERROR_MIDDLEWARE_NOT_BOUND,
+            MbmsErrors.InitializationErrors.ERROR_APP_PERMISSIONS_NOT_GRANTED,
+            MbmsErrors.InitializationErrors.ERROR_DUPLICATE_INITIALIZE,
+            MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
+            MbmsErrors.GeneralErrors.ERROR_MIDDLEWARE_NOT_YET_READY,
+            MbmsErrors.GeneralErrors.ERROR_OUT_OF_MEMORY,
+            MbmsErrors.GeneralErrors.ERROR_MIDDLEWARE_TEMPORARILY_UNAVAILABLE,
+            MbmsErrors.GeneralErrors.ERROR_IN_E911,
+            MbmsErrors.GeneralErrors.ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE,
+            MbmsErrors.GeneralErrors.ERROR_UNABLE_TO_READ_SIM,
+            MbmsErrors.GeneralErrors.ERROR_CARRIER_CHANGE_NOT_ALLOWED}, prefix = { "ERROR_" })
+    private @interface GroupCallError{}
+
+    /**
+     * Called by the middleware when it has detected an error condition. The 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(@GroupCallError int errorCode, @Nullable String message) {
+    }
+
+    /**
+     * Indicates that the list of currently available SAIs has been updated. The app may use this
+     * information to filter the list of group calls when displaying available group calls to
+     * the user by matching the SAIs with a list of group calls separately negotiated with the
+     * carrier. The app may also report the aggregate list of SAIs to the group call application
+     * server so that a network entity can determine when, and where to activate the broadcast of
+     * particular group calls.
+     * @param currentSais The available SAIs on the current cell.
+     * @param availableSais A list of lists of available SAIS in neighboring cells, where each list
+     *                      contains the available SAIs in an individual cell.
+     */
+    public void onAvailableSaisUpdated(List<Integer> currentSais,
+            List<List<Integer>> availableSais) {
+    }
+
+    /**
+     * Called soon after the app calls {@link MbmsGroupCallSession#create}. The information supplied
+     * via this callback may be used to establish a data-link interface with the modem before the
+     * middleware is ready.
+     * Note that this method may be called before {@link #onMiddlewareReady()}.
+     *
+     * @param interfaceName The interface name for the data link.
+     * @param index The index for the data link.
+     */
+    public void onServiceInterfaceAvailable(String interfaceName, int index) {
+    }
+
+    /**
+     * 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 MbmsGroupCallSession} 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() {
+    }
+}
diff --git a/telephony/java/android/telephony/mbms/MbmsUtils.java b/telephony/java/android/telephony/mbms/MbmsUtils.java
index 06b2120..95b4d37 100644
--- a/telephony/java/android/telephony/mbms/MbmsUtils.java
+++ b/telephony/java/android/telephony/mbms/MbmsUtils.java
@@ -23,6 +23,7 @@
 import android.content.pm.*;
 import android.content.pm.ServiceInfo;
 import android.telephony.MbmsDownloadSession;
+import android.telephony.MbmsGroupCallSession;
 import android.telephony.MbmsStreamingSession;
 import android.util.Log;
 
@@ -59,6 +60,9 @@
             case MbmsStreamingSession.MBMS_STREAMING_SERVICE_ACTION:
                 metaDataKey = MbmsStreamingSession.MBMS_STREAMING_SERVICE_OVERRIDE_METADATA;
                 break;
+            case MbmsGroupCallSession.MBMS_GROUP_CALL_SERVICE_ACTION:
+                metaDataKey = MbmsGroupCallSession.MBMS_GROUP_CALL_SERVICE_OVERRIDE_METADATA;
+                break;
         }
         if (metaDataKey == null) {
             return null;
diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsGroupCallService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsGroupCallService.aidl
new file mode 100755
index 0000000..721256a
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/vendor/IMbmsGroupCallService.aidl
@@ -0,0 +1,39 @@
+/*
+** Copyright 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.
+*/
+
+package android.telephony.mbms.vendor;
+
+import android.net.Uri;
+import android.telephony.mbms.IMbmsGroupCallSessionCallback;
+import android.telephony.mbms.IGroupCallCallback;
+
+/**
+ * @hide
+ */
+interface IMbmsGroupCallService
+{
+    int initialize(IMbmsGroupCallSessionCallback callback, int subId);
+
+    void stopGroupCall(int subId, long tmgi);
+
+    void updateGroupCall(int subscriptionId, long tmgi, in int[] saiArray,
+        in int[] frequencyArray);
+
+    int startGroupCall(int subscriptionId, long tmgi, in int[] saiArray,
+        in int[] frequencyArray, IGroupCallCallback callback);
+
+    void dispose(int subId);
+}
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsGroupCallServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsGroupCallServiceBase.java
new file mode 100644
index 0000000..3734ca7
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsGroupCallServiceBase.java
@@ -0,0 +1,275 @@
+/*
+ * 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 android.telephony.mbms.vendor;
+
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.telephony.mbms.GroupCallCallback;
+import android.telephony.mbms.IGroupCallCallback;
+import android.telephony.mbms.IMbmsGroupCallSessionCallback;
+import android.telephony.mbms.MbmsErrors;
+import android.telephony.mbms.MbmsGroupCallSessionCallback;
+import android.telephony.mbms.vendor.IMbmsGroupCallService.Stub;
+
+import java.util.List;
+
+/**
+ * Base class for MBMS group-call services. The middleware should override this class to implement
+ * its {@link Service} for group calls
+ * Subclasses should call this class's {@link #onBind} method to obtain an {@link IBinder} if they
+ * override {@link #onBind}.
+ * @hide
+ */
+@SystemApi
+@TestApi
+public class MbmsGroupCallServiceBase extends Service {
+    private final IBinder mInterface = new Stub() {
+        @Override
+        public int initialize(final IMbmsGroupCallSessionCallback callback,
+                final int subscriptionId) throws RemoteException {
+            if (callback == null) {
+                throw new NullPointerException("Callback must not be null");
+            }
+
+            final int uid = Binder.getCallingUid();
+
+            int result = MbmsGroupCallServiceBase.this.initialize(
+                    new MbmsGroupCallSessionCallback() {
+                        @Override
+                        public void onError(final int errorCode, final String message) {
+                            try {
+                                if (errorCode == MbmsErrors.UNKNOWN) {
+                                    throw new IllegalArgumentException(
+                                            "Middleware cannot send an unknown error.");
+                                }
+                                callback.onError(errorCode, message);
+                            } catch (RemoteException e) {
+                                onAppCallbackDied(uid, subscriptionId);
+                            }
+                        }
+
+                        @Override
+                        public void onAvailableSaisUpdated(final List currentSais,
+                                final List availableSais) {
+                            try {
+                                callback.onAvailableSaisUpdated(currentSais, availableSais);
+                            } catch (RemoteException e) {
+                                onAppCallbackDied(uid, subscriptionId);
+                            }
+                        }
+
+                        @Override
+                        public void onServiceInterfaceAvailable(final String interfaceName,
+                                final int index) {
+                            try {
+                                callback.onServiceInterfaceAvailable(interfaceName, index);
+                            } catch (RemoteException e) {
+                                onAppCallbackDied(uid, subscriptionId);
+                            }
+                        }
+
+                        @Override
+                        public void onMiddlewareReady() {
+                            try {
+                                callback.onMiddlewareReady();
+                            } catch (RemoteException e) {
+                                onAppCallbackDied(uid, subscriptionId);
+                            }
+                        }
+                    }, subscriptionId);
+
+            if (result == MbmsErrors.SUCCESS) {
+                callback.asBinder().linkToDeath(new DeathRecipient() {
+                    @Override
+                    public void binderDied() {
+                        onAppCallbackDied(uid, subscriptionId);
+                    }
+                }, 0);
+            }
+
+            return result;
+        }
+
+        @Override
+        public void stopGroupCall(int subId, long tmgi) {
+            MbmsGroupCallServiceBase.this.stopGroupCall(subId, tmgi);
+        }
+
+        @Override
+        public void updateGroupCall(int subscriptionId, long tmgi, int[] saiArray,
+                int[] frequencyArray) {
+            MbmsGroupCallServiceBase.this.updateGroupCall(
+                    subscriptionId, tmgi, saiArray, frequencyArray);
+        }
+
+        @Override
+        public int startGroupCall(final int subscriptionId, final long tmgi, final int[] saiArray,
+                final int[] frequencyArray, final IGroupCallCallback callback)
+                throws RemoteException {
+            if (callback == null) {
+                throw new NullPointerException("Callback must not be null");
+            }
+
+            final int uid = Binder.getCallingUid();
+
+            int result = MbmsGroupCallServiceBase.this.startGroupCall(
+                    subscriptionId, tmgi, saiArray, frequencyArray, new GroupCallCallback() {
+                        @Override
+                        public void onError(final int errorCode, final String message) {
+                            try {
+                                if (errorCode == MbmsErrors.UNKNOWN) {
+                                    throw new IllegalArgumentException(
+                                            "Middleware cannot send an unknown error.");
+                                }
+                                callback.onError(errorCode, message);
+                            } catch (RemoteException e) {
+                                onAppCallbackDied(uid, subscriptionId);
+                            }
+                        }
+
+                        public void onGroupCallStateChanged(int state, int reason) {
+                            try {
+                                callback.onGroupCallStateChanged(state, reason);
+                            } catch (RemoteException e) {
+                                onAppCallbackDied(uid, subscriptionId);
+                            }
+                        }
+
+                        public void onBroadcastSignalStrengthUpdated(int signalStrength) {
+                            try {
+                                callback.onBroadcastSignalStrengthUpdated(signalStrength);
+                            } catch (RemoteException e) {
+                                onAppCallbackDied(uid, subscriptionId);
+                            }
+                        }
+                    });
+
+            if (result == MbmsErrors.SUCCESS) {
+                callback.asBinder().linkToDeath(new DeathRecipient() {
+                    @Override
+                    public void binderDied() {
+                        onAppCallbackDied(uid, subscriptionId);
+                    }
+                }, 0);
+            }
+
+            return result;
+        }
+
+        @Override
+        public void dispose(int subId) throws RemoteException {
+            MbmsGroupCallServiceBase.this.dispose(subId);
+        }
+    };
+
+    /**
+     * Initialize the group call service for this app and subscription ID, registering the callback.
+     *
+     * May throw an {@link IllegalArgumentException} or a {@link SecurityException}, which
+     * will be intercepted and passed to the app as
+     * {@link MbmsErrors.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
+     *
+     * May return any value from {@link MbmsErrors.InitializationErrors}
+     * or {@link MbmsErrors#SUCCESS}. Non-successful error codes will be passed to the app via
+     * {@link IMbmsGroupCallSessionCallback#onError(int, String)}.
+     *
+     * @param callback The callback to use to communicate with the app.
+     * @param subscriptionId The subscription ID to use.
+     */
+    public int initialize(MbmsGroupCallSessionCallback callback, int subscriptionId)
+            throws RemoteException {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    /**
+     * Starts a particular group call. This method may perform asynchronous work. When
+     * the call is ready for consumption, the middleware should inform the app via
+     * {@link IGroupCallCallback#onGroupCallStateChanged(int, int)}.
+     *
+     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
+     *
+     * @param subscriptionId The subscription id to use.
+     * @param tmgi The TMGI, an identifier for the group call.
+     * @param saiArray An array of SAIs for the group call.
+     * @param frequencyArray An array of frequencies for the group call.
+     * @param callback The callback object on which the app wishes to receive updates.
+     * @return Any error in {@link MbmsErrors.GeneralErrors}
+     */
+    public int startGroupCall(int subscriptionId, long tmgi, int[] saiArray, int[] frequencyArray,
+            GroupCallCallback callback) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    /**
+     * Stop the group call identified by {@code tmgi}.
+     *
+     * The callback provided via {@link #startGroupCall} should no longer be
+     * used after this method has called by the app.
+     *
+     * May throw an {@link IllegalStateException}
+     *
+     * @param subscriptionId The subscription id to use.
+     * @param tmgi The TMGI for the call to stop.
+     */
+    public void stopGroupCall(int subscriptionId, long tmgi) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    /**
+     * Called when the app receives new SAI and frequency information for the group call identified
+     * by {@code tmgi}.
+     * @param saiArray New array of SAIs that the call is available on.
+     * @param frequencyArray New array of frequencies that the call is available on.
+     */
+    public void updateGroupCall(int subscriptionId, long tmgi, int[] saiArray,
+            int[] frequencyArray) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    /**
+     * 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} should no longer be used
+     * after this method has been called by the app.
+     *
+     * May throw an {@link IllegalStateException}
+     *
+     * @param subscriptionId The subscription id to use.
+     */
+    public void dispose(int subscriptionId) throws RemoteException {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    /**
+     * Indicates that the app identified by the given UID and subscription ID has died.
+     * @param uid the UID of the app, as returned by {@link Binder#getCallingUid()}.
+     * @param subscriptionId The subscription ID the app is using.
+     */
+    public void onAppCallbackDied(int uid, int subscriptionId) {
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mInterface;
+    }
+}
diff --git a/tests/DexLoggerIntegrationTests/AndroidManifest.xml b/tests/DexLoggerIntegrationTests/AndroidManifest.xml
index a847e8f..a9f01ed 100644
--- a/tests/DexLoggerIntegrationTests/AndroidManifest.xml
+++ b/tests/DexLoggerIntegrationTests/AndroidManifest.xml
@@ -17,10 +17,10 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.frameworks.dexloggertest">
 
-    <!-- Tests feature introduced in P (27) -->
+    <!-- Tests feature introduced in P (28) -->
     <uses-sdk
-        android:minSdkVersion="27"
-        android:targetSdkVersion="27" />
+        android:minSdkVersion="28"
+        android:targetSdkVersion="28" />
 
     <uses-permission android:name="android.permission.READ_LOGS" />