1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.wm.shell.pip; 18 19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 21 import static android.util.TypedValue.COMPLEX_UNIT_DIP; 22 23 import android.annotation.Nullable; 24 import android.app.ActivityTaskManager; 25 import android.app.ActivityTaskManager.RootTaskInfo; 26 import android.app.RemoteAction; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.os.RemoteException; 30 import android.util.DisplayMetrics; 31 import android.util.Log; 32 import android.util.Pair; 33 import android.util.TypedValue; 34 import android.window.TaskSnapshot; 35 36 import com.android.internal.protolog.common.ProtoLog; 37 import com.android.wm.shell.protolog.ShellProtoLogGroup; 38 39 import java.util.List; 40 import java.util.Objects; 41 42 /** A class that includes convenience methods. */ 43 public class PipUtils { 44 private static final String TAG = "PipUtils"; 45 46 // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal. 47 private static final double EPSILON = 1e-7; 48 49 /** 50 * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack. 51 * The component name may be null if no such activity exists. 52 */ getTopPipActivity(Context context)53 public static Pair<ComponentName, Integer> getTopPipActivity(Context context) { 54 try { 55 final String sysUiPackageName = context.getPackageName(); 56 final RootTaskInfo pinnedTaskInfo = ActivityTaskManager.getService().getRootTaskInfo( 57 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); 58 if (pinnedTaskInfo != null && pinnedTaskInfo.childTaskIds != null 59 && pinnedTaskInfo.childTaskIds.length > 0) { 60 for (int i = pinnedTaskInfo.childTaskNames.length - 1; i >= 0; i--) { 61 ComponentName cn = ComponentName.unflattenFromString( 62 pinnedTaskInfo.childTaskNames[i]); 63 if (cn != null && !cn.getPackageName().equals(sysUiPackageName)) { 64 return new Pair<>(cn, pinnedTaskInfo.childTaskUserIds[i]); 65 } 66 } 67 } 68 } catch (RemoteException e) { 69 ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 70 "%s: Unable to get pinned stack.", TAG); 71 } 72 return new Pair<>(null, 0); 73 } 74 75 /** 76 * @return the pixels for a given dp value. 77 */ dpToPx(float dpValue, DisplayMetrics dm)78 public static int dpToPx(float dpValue, DisplayMetrics dm) { 79 return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm); 80 } 81 82 /** 83 * @return true if the aspect ratios differ 84 */ aspectRatioChanged(float aspectRatio1, float aspectRatio2)85 public static boolean aspectRatioChanged(float aspectRatio1, float aspectRatio2) { 86 return Math.abs(aspectRatio1 - aspectRatio2) > EPSILON; 87 } 88 89 /** 90 * Checks whether title, description and intent match. 91 * Comparing icons would be good, but using equals causes false negatives 92 */ remoteActionsMatch(RemoteAction action1, RemoteAction action2)93 public static boolean remoteActionsMatch(RemoteAction action1, RemoteAction action2) { 94 if (action1 == action2) return true; 95 if (action1 == null || action2 == null) return false; 96 return action1.isEnabled() == action2.isEnabled() 97 && action1.shouldShowIcon() == action2.shouldShowIcon() 98 && Objects.equals(action1.getTitle(), action2.getTitle()) 99 && Objects.equals(action1.getContentDescription(), action2.getContentDescription()) 100 && Objects.equals(action1.getActionIntent(), action2.getActionIntent()); 101 } 102 103 /** 104 * Returns true if the actions in the lists match each other according to {@link 105 * PipUtils#remoteActionsMatch(RemoteAction, RemoteAction)}, including their position. 106 */ remoteActionsChanged(List<RemoteAction> list1, List<RemoteAction> list2)107 public static boolean remoteActionsChanged(List<RemoteAction> list1, List<RemoteAction> list2) { 108 if (list1 == null && list2 == null) { 109 return false; 110 } 111 if (list1 == null || list2 == null) { 112 return true; 113 } 114 if (list1.size() != list2.size()) { 115 return true; 116 } 117 for (int i = 0; i < list1.size(); i++) { 118 if (!remoteActionsMatch(list1.get(i), list2.get(i))) { 119 return true; 120 } 121 } 122 return false; 123 } 124 125 /** @return {@link TaskSnapshot} for a given task id. */ 126 @Nullable getTaskSnapshot(int taskId, boolean isLowResolution)127 public static TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution) { 128 if (taskId <= 0) return null; 129 try { 130 return ActivityTaskManager.getService().getTaskSnapshot( 131 taskId, isLowResolution, false /* takeSnapshotIfNeeded */); 132 } catch (RemoteException e) { 133 Log.e(TAG, "Failed to get task snapshot, taskId=" + taskId, e); 134 return null; 135 } 136 } 137 } 138