Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6b4ce334 authored by Hall Liu's avatar Hall Liu Committed by Android (Google) Code Review
Browse files

Merge "Add API for uploading call composer pictures"

parents 7fe5eeef 9df4a2db
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -41820,6 +41820,8 @@ package android.telephony {
    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void switchMultiSimConfig(int);
    method public void unregisterPhoneStateListener(@NonNull android.telephony.PhoneStateListener);
    method public void updateAvailableNetworks(@NonNull java.util.List<android.telephony.AvailableNetworkInfo>, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
    method public void uploadCallComposerPicture(@NonNull java.nio.file.Path, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.ParcelUuid,android.telephony.TelephonyManager.CallComposerException>);
    method public void uploadCallComposerPicture(@NonNull java.io.InputStream, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.ParcelUuid,android.telephony.TelephonyManager.CallComposerException>);
    field public static final String ACTION_CARRIER_MESSAGING_CLIENT_SERVICE = "android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE";
    field public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE = "android.telephony.action.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE";
    field public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE = "android.telephony.action.CARRIER_SIGNAL_PCO_VALUE";
@@ -41962,6 +41964,18 @@ package android.telephony {
    field public static final String VVM_TYPE_OMTP = "vvm_type_omtp";
  }
  public static class TelephonyManager.CallComposerException extends java.lang.Exception {
    ctor public TelephonyManager.CallComposerException(int, @Nullable java.io.IOException);
    method public int getErrorCode();
    method @Nullable public java.io.IOException getIOException();
    field public static final int ERROR_AUTHENTICATION_FAILED = 3; // 0x3
    field public static final int ERROR_FILE_TOO_LARGE = 2; // 0x2
    field public static final int ERROR_INPUT_CLOSED = 4; // 0x4
    field public static final int ERROR_IO_EXCEPTION = 5; // 0x5
    field public static final int ERROR_REMOTE_END_CLOSED = 1; // 0x1
    field public static final int ERROR_UNKNOWN = 0; // 0x0
  }
  public abstract static class TelephonyManager.CellInfoCallback {
    ctor public TelephonyManager.CellInfoCallback();
    method public abstract void onCellInfo(@NonNull java.util.List<android.telephony.CellInfo>);
+1 −0
Original line number Diff line number Diff line
@@ -8568,6 +8568,7 @@ package android.provider {
  }
  public static class CallLog.CallComposerLoggingException extends java.lang.Throwable {
    ctor public CallLog.CallComposerLoggingException(int);
    method public int getErrorCode();
    field public static final int ERROR_INPUT_CLOSED = 3; // 0x3
    field public static final int ERROR_REMOTE_END_CLOSED = 1; // 0x1
+0 −1
Original line number Diff line number Diff line
@@ -148,7 +148,6 @@ public class CallLog {

        private final int mErrorCode;

        /** @hide */
        public CallComposerLoggingException(@CallComposerLoggingError int errorCode) {
            mErrorCode = errorCode;
        }
+346 −3
Original line number Diff line number Diff line
@@ -57,8 +57,10 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.OutcomeReceiver;
import android.os.ParcelFileDescriptor;
import android.os.ParcelUuid;
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.Process;
@@ -119,8 +121,13 @@ import com.android.internal.telephony.RILConstants;
import com.android.internal.telephony.SmsApplication;
import com.android.telephony.Rlog;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -554,12 +561,12 @@ public class TelephonyManager {
    private static final int MAXIMUM_CALL_COMPOSER_PICTURE_SIZE = 80000;
    // TODO(hallliu): link to upload method in docs
    /**
     * Indicates the maximum size of the call composure picture.
     *
     * Pictures sent via uploadCallComposerPicture must not exceed this size, or an
     * {@link IllegalArgumentException} will be thrown.
     * Pictures sent via {@link #uploadCallComposerPicture(InputStream, Executor, OutcomeReceiver)}
     * or {@link #uploadCallComposerPicture(Path, Executor, OutcomeReceiver)} must not exceed this
     * size, or an error will be returned via the callback in those methods.
     *
     * @return Maximum file size in bytes.
     */
@@ -4242,6 +4249,342 @@ public class TelephonyManager {
        }
    }
    /**
     * Exception that may be supplied to the callback in {@link #uploadCallComposerPicture} if
     * something goes awry.
     */
    public static class CallComposerException extends Exception {
        /**
         * Used internally only, signals success of the upload to the carrier.
         * @hide
         */
        public static final int SUCCESS = -1;
        /**
         * Indicates that an unknown error was encountered when uploading the call composer picture.
         *
         * Clients that encounter this error should retry the upload.
         */
        public static final int ERROR_UNKNOWN = 0;
        /**
         * Indicates that the phone process died or otherwise became unavailable while uploading the
         * call composer picture.
         *
         * Clients that encounter this error should retry the upload.
         */
        public static final int ERROR_REMOTE_END_CLOSED = 1;
        /**
         * Indicates that the file or stream supplied exceeds the size limit defined in
         * {@link #getMaximumCallComposerPictureSize()}.
         *
         * Clients that encounter this error should retry the upload after reducing the size of the
         * picture.
         */
        public static final int ERROR_FILE_TOO_LARGE = 2;
        /**
         * Indicates that the device failed to authenticate with the carrier when uploading the
         * picture.
         *
         * Clients that encounter this error should not retry the upload unless a reboot or radio
         * reset has been performed in the interim.
         */
        public static final int ERROR_AUTHENTICATION_FAILED = 3;
        /**
         * Indicates that the {@link InputStream} passed to {@link #uploadCallComposerPicture}
         * was closed.
         *
         * The caller should retry if this error is encountered, and be sure to not close the stream
         * before the callback is called this time.
         */
        public static final int ERROR_INPUT_CLOSED = 4;
        /**
         * Indicates that an {@link IOException} was encountered while reading the picture.
         *
         * The offending {@link IOException} will be available via {@link #getIOException()}.
         * Clients should use the contents of the exception to determine whether a retry is
         * warranted.
         */
        public static final int ERROR_IO_EXCEPTION = 5;
        /** @hide */
        @IntDef(prefix = {"ERROR_"}, value = {
                ERROR_UNKNOWN,
                ERROR_REMOTE_END_CLOSED,
                ERROR_FILE_TOO_LARGE,
                ERROR_AUTHENTICATION_FAILED,
                ERROR_INPUT_CLOSED,
                ERROR_IO_EXCEPTION,
        })
        @Retention(RetentionPolicy.SOURCE)
        public @interface CallComposerError {}
        private final int mErrorCode;
        private final IOException mIOException;
        public CallComposerException(@CallComposerError int errorCode,
                @Nullable IOException ioException) {
            mErrorCode = errorCode;
            mIOException = ioException;
        }
        /**
         * Fetches the error code associated with this exception.
         * @return An error code.
         */
        public @CallComposerError int getErrorCode() {
            return mErrorCode;
        }
        /**
         * Fetches the {@link IOException} that caused the error.
         */
        // Follows the naming of IOException
        @SuppressLint("AcronymName")
        public @Nullable IOException getIOException() {
            return mIOException;
        }
    }
    /** @hide */
    public static final String KEY_CALL_COMPOSER_PICTURE_HANDLE = "call_composer_picture_handle";
    /**
     * Uploads a picture to the carrier network for use with call composer.
     *
     * @see #uploadCallComposerPicture(InputStream, Executor, OutcomeReceiver)
     * @param pictureToUpload Path to a local file containing the picture to upload.
     * @param executor The {@link Executor} on which the {@code pictureToUpload} file will be read
     *                 from disk, as well as on which {@code callback} will be called.
     * @param callback A callback called when the upload operation terminates, either in success
     *                 or in error.
     */
    public void uploadCallComposerPicture(@NonNull Path pictureToUpload,
            @CallbackExecutor @NonNull Executor executor,
            @NonNull OutcomeReceiver<ParcelUuid, CallComposerException> callback) {
        Objects.requireNonNull(pictureToUpload);
        Objects.requireNonNull(executor);
        Objects.requireNonNull(callback);
        // Do the role check now so that we can quit early if needed -- there's an additional
        // permission check on the other side of the binder call as well.
        RoleManager rm = mContext.getSystemService(RoleManager.class);
        if (!rm.isRoleHeld(RoleManager.ROLE_DIALER)) {
            throw new SecurityException("You must hold RoleManager.ROLE_DIALER to do this");
        }
        executor.execute(() -> {
            try {
                if (Looper.getMainLooper().isCurrentThread()) {
                    Log.w(TAG, "Uploading call composer picture on main thread!"
                            + " hic sunt dracones!");
                }
                long size = Files.size(pictureToUpload);
                if (size > getMaximumCallComposerPictureSize()) {
                    callback.onError(new CallComposerException(
                            CallComposerException.ERROR_FILE_TOO_LARGE, null));
                    return;
                }
                InputStream fileStream = Files.newInputStream(pictureToUpload);
                try {
                    uploadCallComposerPicture(fileStream, executor,
                            new OutcomeReceiver<ParcelUuid, CallComposerException>() {
                                @Override
                                public void onResult(ParcelUuid result) {
                                    try {
                                        fileStream.close();
                                    } catch (IOException e) {
                                        // ignore
                                        Log.e(TAG, "Error closing file input stream when"
                                                + " uploading call composer pic");
                                    }
                                    callback.onResult(result);
                                }
                                @Override
                                public void onError(CallComposerException error) {
                                    try {
                                        fileStream.close();
                                    } catch (IOException e) {
                                        // ignore
                                        Log.e(TAG, "Error closing file input stream when"
                                                + " uploading call composer pic");
                                    }
                                    callback.onError(error);
                                }
                            });
                } catch (Exception e) {
                    Log.e(TAG, "Got exception calling into stream-version of"
                            + " uploadCallComposerPicture: " + e);
                    try {
                        fileStream.close();
                    } catch (IOException e1) {
                        // ignore
                        Log.e(TAG, "Error closing file input stream when uploading"
                                + " call composer pic");
                    }
                }
            } catch (IOException e) {
                Log.e(TAG, "IOException when uploading call composer pic:" + e);
                callback.onError(
                        new CallComposerException(CallComposerException.ERROR_IO_EXCEPTION, e));
            }
        });
    }
    /**
     * Uploads a picture to the carrier network for use with call composer.
     *
     * This method allows a dialer app to upload a picture to the carrier network that can then
     * later be attached to an outgoing call. In order to attach the picture to a call, use the
     * {@link ParcelUuid} returned from {@code callback} upon successful upload as the value to
     * {@link TelecomManager#EXTRA_OUTGOING_PICTURE}.
     *
     * This functionality is only available to the app filling the {@link RoleManager#ROLE_DIALER}
     * role on the device.
     *
     * @param pictureToUpload An {@link InputStream} that supplies the bytes representing the
     *                        picture to upload. The client bears responsibility for closing this
     *                        stream after {@code callback} is called with success or failure.
     *
     *                        Additionally, if the stream supplies more bytes than the return value
     *                        of {@link #getMaximumCallComposerPictureSize()}, the upload will be
     *                        aborted and the callback will be called with an exception containing
     *                        {@link CallComposerException#ERROR_FILE_TOO_LARGE}.
     * @param executor The {@link Executor} on which the {@code pictureToUpload} stream will be
     *                 read, as well as on which the callback will be called.
     * @param callback A callback called when the upload operation terminates, either in success
     *                 or in error.
     */
    public void uploadCallComposerPicture(@NonNull InputStream pictureToUpload,
            @CallbackExecutor @NonNull Executor executor,
            @NonNull OutcomeReceiver<ParcelUuid, CallComposerException> callback) {
        Objects.requireNonNull(pictureToUpload);
        Objects.requireNonNull(executor);
        Objects.requireNonNull(callback);
        ITelephony telephony = getITelephony();
        if (telephony == null) {
            throw new IllegalStateException("Telephony service not available.");
        }
        ParcelFileDescriptor writeFd;
        ParcelFileDescriptor readFd;
        try {
            ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createReliablePipe();
            writeFd = pipe[1];
            readFd = pipe[0];
        } catch (IOException e) {
            executor.execute(() -> callback.onError(
                    new CallComposerException(CallComposerException.ERROR_IO_EXCEPTION, e)));
            return;
        }
        OutputStream output = new ParcelFileDescriptor.AutoCloseOutputStream(writeFd);
        try {
            telephony.uploadCallComposerPicture(getSubId(), mContext.getOpPackageName(),
                    readFd, new ResultReceiver(null) {
                        @Override
                        protected void onReceiveResult(int resultCode, Bundle result) {
                            if (resultCode != CallComposerException.SUCCESS) {
                                executor.execute(() -> callback.onError(
                                        new CallComposerException(resultCode, null)));
                                return;
                            }
                            ParcelUuid resultUuid =
                                    result.getParcelable(KEY_CALL_COMPOSER_PICTURE_HANDLE);
                            if (resultUuid == null) {
                                Log.e(TAG, "Got null uuid without an error"
                                        + " while uploading call composer pic");
                                executor.execute(() -> callback.onError(
                                        new CallComposerException(
                                                CallComposerException.ERROR_UNKNOWN, null)));
                                return;
                            }
                            executor.execute(() -> callback.onResult(resultUuid));
                        }
                    });
        } catch (RemoteException e) {
            Log.e(TAG, "Remote exception uploading call composer pic:" + e);
            e.rethrowAsRuntimeException();
        }
        executor.execute(() -> {
            if (Looper.getMainLooper().isCurrentThread()) {
                Log.w(TAG, "Uploading call composer picture on main thread!"
                        + " hic sunt dracones!");
            }
            int totalBytesRead = 0;
            byte[] buffer = new byte[16 * 1024];
            try {
                while (true) {
                    int numRead;
                    try {
                        numRead = pictureToUpload.read(buffer);
                    } catch (IOException e) {
                        Log.e(TAG, "IOException reading from input while uploading pic: " + e);
                        // Most likely, this was because the stream was closed. We have no way to
                        // tell though.
                        callback.onError(new CallComposerException(
                                CallComposerException.ERROR_INPUT_CLOSED, e));
                        try {
                            writeFd.closeWithError("input closed");
                        } catch (IOException e1) {
                            // log and ignore
                            Log.e(TAG, "Error closing fd pipe: " + e1);
                        }
                        break;
                    }
                    if (numRead < 0) {
                        break;
                    }
                    totalBytesRead += numRead;
                    if (totalBytesRead > getMaximumCallComposerPictureSize()) {
                        Log.e(TAG, "Read too many bytes from call composer pic stream: "
                                + totalBytesRead);
                        try {
                            callback.onError(new CallComposerException(
                                    CallComposerException.ERROR_FILE_TOO_LARGE, null));
                            writeFd.closeWithError("too large");
                        } catch (IOException e1) {
                            // log and ignore
                            Log.e(TAG, "Error closing fd pipe: " + e1);
                        }
                        break;
                    }
                    try {
                        output.write(buffer, 0, numRead);
                    } catch (IOException e) {
                        callback.onError(new CallComposerException(
                                CallComposerException.ERROR_REMOTE_END_CLOSED, e));
                        try {
                            writeFd.closeWithError("remote end closed");
                        } catch (IOException e1) {
                            // log and ignore
                            Log.e(TAG, "Error closing fd pipe: " + e1);
                        }
                        break;
                    }
                }
            } finally {
                try {
                    output.close();
                } catch (IOException e) {
                    // Ignore -- we might've already closed it.
                }
            }
        });
    }
    /**
     * Returns the Group Identifier Level1 for a GSM phone.
     * Return null if it is unavailable.
+3 −0
Original line number Diff line number Diff line
@@ -1261,6 +1261,9 @@ interface ITelephony {
     */
    int getRadioAccessFamily(in int phoneId, String callingPackage);

    void uploadCallComposerPicture(int subscriptionId, String callingPackage,
            in ParcelFileDescriptor fd, in ResultReceiver callback);

    /**
     * Enables or disables video calling.
     *