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

Commit 58f45d9f authored by Vadim Caen's avatar Vadim Caen Committed by Android (Google) Code Review
Browse files

Merge changes from topic "tab-sharing-ui" into main

* changes:
  Media Projection self content sharing (1/n)
  Add MANAGE_MEDIA_PROJECTION to Shell permission
parents 14efc21b 8d92b8e5
Loading
Loading
Loading
Loading
+21 −0
Original line number Diff line number Diff line
@@ -27364,6 +27364,25 @@ package android.media.midi {
package android.media.projection {
  @FlaggedApi("com.android.media.projection.flags.app_content_sharing") public abstract class AppContentProjectionService extends android.app.Service {
    ctor public AppContentProjectionService();
    method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
    method public abstract void onContentRequest(@NonNull android.media.projection.AppContentRequest);
    method public abstract void onContentRequestCanceled();
    method public abstract boolean onLoopbackProjectionStarted(@NonNull android.media.projection.AppContentProjectionSession, int);
    method public abstract void onSessionStopped(@NonNull android.media.projection.AppContentProjectionSession);
    field public static final String SERVICE_INTERFACE = "android.media.projection.AppContentProjectionService";
  }
  @FlaggedApi("com.android.media.projection.flags.app_content_sharing") public class AppContentProjectionSession {
    method public void notifySessionStop();
  }
  @FlaggedApi("com.android.media.projection.flags.app_content_sharing") public final class AppContentRequest {
    method @NonNull public android.util.Size getThumbnailSize();
    method public void provideContent(@NonNull java.util.List<android.media.projection.MediaProjectionAppContent>);
  }
  public final class MediaProjection {
    method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, int, @Nullable android.view.Surface, @Nullable android.hardware.display.VirtualDisplay.Callback, @Nullable android.os.Handler);
    method public void registerCallback(@NonNull android.media.projection.MediaProjection.Callback, @Nullable android.os.Handler);
@@ -27392,6 +27411,7 @@ package android.media.projection {
    method @FlaggedApi("com.android.media.projection.flags.app_content_sharing") public int getInitiallySelectedSource();
    method @FlaggedApi("com.android.media.projection.flags.app_content_sharing") public int getProjectionSources();
    method @FlaggedApi("com.android.media.projection.flags.app_content_sharing") @Nullable public CharSequence getRequesterHint();
    method @FlaggedApi("com.android.media.projection.flags.app_content_sharing") public boolean isOwnAppContentProvided();
    method @FlaggedApi("com.android.media.projection.flags.app_content_sharing") public boolean isSourceEnabled(int);
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.media.projection.MediaProjectionConfig> CREATOR;
@@ -27404,6 +27424,7 @@ package android.media.projection {
    ctor public MediaProjectionConfig.Builder();
    method @NonNull public android.media.projection.MediaProjectionConfig build();
    method @NonNull public android.media.projection.MediaProjectionConfig.Builder setInitiallySelectedSource(int);
    method @NonNull public android.media.projection.MediaProjectionConfig.Builder setOwnAppContentProvided(@NonNull android.content.Context, boolean);
    method @NonNull public android.media.projection.MediaProjectionConfig.Builder setRequesterHint(@Nullable String);
    method @NonNull public android.media.projection.MediaProjectionConfig.Builder setSourceEnabled(int, boolean);
  }
+203 −0
Original line number Diff line number Diff line
/*
 * Copyright 2025 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.media.projection;

import android.annotation.EnforcePermission;
import android.annotation.FlaggedApi;
import android.annotation.SdkConstant;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PermissionEnforcer;
import android.os.RemoteCallback;
import android.util.Size;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.media.projection.flags.Flags;

import java.util.List;

/**
 * Service to be implemented by the application providing the {@link MediaProjectionAppContent}.
 *
 * <p>
 * To receive media projection callbacks related to app content sharing, this service must:
 * <ol>
 *     <li> be declared with an intent-filter action
 *     {@value AppContentProjectionService#SERVICE_INTERFACE}
 *     <li> set {@code android:exported="true"}
 *     <li> be protected by {@value android.Manifest.permission#MANAGE_MEDIA_PROJECTION}
 *     <li> set
 *     {@link MediaProjectionConfig.Builder#setOwnAppContentProvided(Context, boolean)} to
 *     {@code true}
 * </ol>
 * <p>
 * <pre>
 *  &lt;/application>
 *   ...
 *    &lt;service
 *         android:exported="true"
 *         android:name="com.example.AppContentSharingService"
 *         android:permission="android.permission.MANAGE_MEDIA_PROJECTION">
 *       &lt;intent-filter>
 *         &lt;action android:name="android.media.projection.AppContentProjectionService"/>
 *       &lt;/intent-filter>
 *     &lt;/service>
 *   &lt;/application>
 * </pre>
 * <p>
 * Only holders of {@value android.Manifest.permission#MANAGE_MEDIA_PROJECTION} are allowed to call
 * these callbacks.
 */
@FlaggedApi(Flags.FLAG_APP_CONTENT_SHARING)
public abstract class AppContentProjectionService extends Service {

    /**
     * The {@link Intent} action that must be declared as handled by the service.
     * Put this in your manifest to reply to requests for app content projection.
     */
    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
    public static final String SERVICE_INTERFACE =
            "android.media.projection.AppContentProjectionService";
    /** @hide **/
    public static final String EXTRA_APP_CONTENT = "extra_app_content";

    private AppContentProjectionCallbackInternal mService;

    private class AppContentProjectionCallbackInternal extends
            IAppContentProjectionCallback.Stub {

        private AppContentProjectionSession mSession;

        private AppContentProjectionCallbackInternal(PermissionEnforcer permissionEnforcer) {
            super(permissionEnforcer);
        }

        @Override
        @EnforcePermission(allOf = {"MANAGE_MEDIA_PROJECTION"})
        public void onContentRequest(RemoteCallback newContentConsumer, int width, int height) {
            onContentRequest_enforcePermission();
            AppContentProjectionService.this.onContentRequest(
                    new AppContentRequest(new Size(width, height), mediaProjectionAppContents -> {
                        Bundle bundle = new Bundle();
                        bundle.putParcelableArray(EXTRA_APP_CONTENT,
                                mediaProjectionAppContents);
                        newContentConsumer.sendResult(bundle);
                    }));
        }

        @Override
        @EnforcePermission(allOf = {"MANAGE_MEDIA_PROJECTION"})
        public void onLoopbackProjectionStarted(IAppContentProjectionSession session,
                int contentId) {
            onLoopbackProjectionStarted_enforcePermission();
            if (mSession != null) {
                throw new IllegalStateException(
                        "Only one single AppContentProjectionSession is supported");
            }
            mSession = new AppContentProjectionSession(session);
            AppContentProjectionService.this.onLoopbackProjectionStarted(
                    mSession, contentId
            );
        }

        @Override
        @EnforcePermission(allOf = {"MANAGE_MEDIA_PROJECTION"})
        public void onSessionStopped() {
            onSessionStopped_enforcePermission();
            AppContentProjectionService.this.onSessionStopped(mSession);
            mSession = null;
        }

        @Override
        @EnforcePermission(allOf = {"MANAGE_MEDIA_PROJECTION"})
        public void onContentRequestCanceled() {
            onContentRequestCanceled_enforcePermission();
            AppContentProjectionService.this.onContentRequestCanceled();
        }
    }

    @NonNull
    @Override
    public final IBinder onBind(@Nullable Intent intent) {
        if (mService == null) {
            mService = new AppContentProjectionCallbackInternal(new PermissionEnforcer(this));
        }
        return mService;
    }

    /**
     * Called when the picker UI has been shown to the user.
     * <p>
     * App content should be returned by calling {@link AppContentRequest#provideContent(List)}
     * <p>
     * If the user picks one of the offered app content,
     * {@link #onLoopbackProjectionStarted(AppContentProjectionSession, int)} will be called with
     * the id corresponding to the chosen content. See {@link MediaProjectionAppContent} for more
     * information about the id.
     * @param request the request instance containing the characteristics of the content requested
     */
    public abstract void onContentRequest(@NonNull AppContentRequest request);

    /**
     * Called when the user picked a content to be shared within the requesting app.
     *
     * <p>This can be called multiple times if the user picks a different content to
     * be shared at a later point.
     *
     * <p> {@code contentId} is the id that has been provided by this application during the
     * {@link #onContentRequest(AppContentRequest)} call. See
     * {@link MediaProjectionAppContent} for more information about the id.
     *
     * @param session the session started for sharing the selected {@link MediaProjectionAppContent}
     * @param contentId the id of the selected {@link MediaProjectionAppContent}
     *
     * @return {@code true} if the request has been fulfilled, {@code false} otherwise
     */
    public abstract boolean onLoopbackProjectionStarted(
            @NonNull AppContentProjectionSession session, int contentId);

    /**
     * Called when the sharing session has been ended by the user or the system. The shared
     * resources can be discarded.
     *
     * <p> The provided session object is the same as the one provided when
     * {@link #onLoopbackProjectionStarted(AppContentProjectionSession, int)} was called. It can
     * be used to identify the session that is being terminated.
     *
     * <p> Note that if the session is terminated by this service by calling
     * {@link AppContentProjectionSession#notifySessionStop()}, this callback won't be called.
     *
     * @param session the same session object that was passed in
     * {@link #onLoopbackProjectionStarted(AppContentProjectionSession, int)}
     */
    public abstract void onSessionStopped(@NonNull AppContentProjectionSession session);

    /**
     * Called when the user didn't pick some app content to be shared. This can happen if the
     * projection request was canceled, or the user picked another source (e.g. display, whole app).
     * <p>
     * Any resources created for sharing app content, such as thumbnails, can be discarded at this
     * point.
     */
    public abstract void onContentRequestCanceled();

}
+55 −0
Original line number Diff line number Diff line
/*
 * Copyright 2025 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.media.projection;

import android.annotation.FlaggedApi;
import android.os.RemoteException;

import com.android.media.projection.flags.Flags;

/**
 * Represents a media projection session where the application receiving an instance of this class
 * is sharing its own content.
 *
 * <p> The application must use this class to notify if the sharing session terminates from its side
 * (e.g. the shared content is not available anymore).
 */
@FlaggedApi(Flags.FLAG_APP_CONTENT_SHARING)
public class AppContentProjectionSession {

    private final IAppContentProjectionSession mSessionInternal;

    /**
     * @hide
     */
    public AppContentProjectionSession(IAppContentProjectionSession sessionInternal) {
        mSessionInternal = sessionInternal;
    }

    /**
     * Notify the system that the content shared is not available anymore and the session must be
     * stopped.
     */
    public void notifySessionStop() {
        try {
            mSessionInternal.notifySessionStop();
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }

    }
}
+73 −0
Original line number Diff line number Diff line
/*
 * Copyright 2025 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.media.projection;

import android.annotation.FlaggedApi;
import android.util.Size;

import androidx.annotation.NonNull;

import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;

/**
 * Contains the requested characteristic for an app content projection.
 * <p>
 * An instance of this class is passed to
 * {@link AppContentProjectionService#onContentRequest(AppContentRequest)}
 * <p>
 * Available content must be returned by calling {@link #provideContent(List)}}
 */
@FlaggedApi(com.android.media.projection.flags.Flags.FLAG_APP_CONTENT_SHARING)
public final class AppContentRequest {

    private final Size mThumbnailSize;
    private final Consumer<MediaProjectionAppContent[]> mContentConsumer;

    /**
     * @hide
     */
    public AppContentRequest(@NonNull Size thumbnailSize,
            @NonNull Consumer<MediaProjectionAppContent[]> contentConsumer) {
        mThumbnailSize = Objects.requireNonNull(thumbnailSize);
        mContentConsumer = Objects.requireNonNull(contentConsumer);
    }

    /**
     * Used to return the content to the requester. The content passed here will replace any
     * previously provided content.
     * <p>
     * If no more content is available to offer, an empty list can be passed.
     */
    public void provideContent(@NonNull List<MediaProjectionAppContent> content) {
        Objects.requireNonNull(content,
                "content must not be null. If all the content needs to be removed, an empty list"
                        + " can be passed");
        mContentConsumer.accept(content.toArray(new MediaProjectionAppContent[0]));
    }

    /**
     * @return The requested thumbnail size, in px, for each {@link MediaProjectionAppContent}
     * item.
     */
    @NonNull
    public Size getThumbnailSize() {
        return mThumbnailSize;
    }

}
+64 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 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.media.projection;

import android.media.projection.MediaProjectionAppContent;
import android.media.projection.IAppContentProjectionSession;
import android.os.RemoteCallback;

/**
* @hide
*/
oneway interface IAppContentProjectionCallback {
     /**
        * Called when the picker UI has been shown to the user.
        *
        * @param newContentConsumer Consumer to update the list of displayed content
        *                           if needed.
        * @param thumbnailWidth The requested width of the app content thumbnail
        * @param thumbnailHeight The requested height of the app content thumbnail
        */
       @EnforcePermission(allOf = {"MANAGE_MEDIA_PROJECTION"})
       void onContentRequest(in RemoteCallback newContentConsumer, int thumbnailWidth, int thumbnailHeight);

       /**
        * Called when the user picked a content to be shared within the requesting app.
        * This can be called multiple time if the user picked a different content to
        * be shared
        *
        * @return true if the request has been fulfilled, false otherwise.
        */
       @EnforcePermission(allOf = {"MANAGE_MEDIA_PROJECTION"})
       void onLoopbackProjectionStarted(in IAppContentProjectionSession session, int contentId);

      /**
       * Called when the sharing session has been ended by the user or the system. The shared
       * resources can be discarded.
       */
       @EnforcePermission(allOf = {"MANAGE_MEDIA_PROJECTION"})
       void onSessionStopped();

      /**
       * Called when the user didn't pick some app content to be shared. This can happen if the
       * projection request was canceled, of the user picked another source (e.g. display, whole app).
       * <p>
       * Any resources created for sharing app content, such as thumbnail, can be discarded at this
       * point.
       */
       @EnforcePermission(allOf = {"MANAGE_MEDIA_PROJECTION"})
       void onContentRequestCanceled();
}
Loading