1 /* 2 * Copyright (C) 2023 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.DeviceAsWebcam.view; 18 19 import android.animation.ObjectAnimator; 20 import android.content.Context; 21 import android.util.AttributeSet; 22 import android.view.LayoutInflater; 23 import android.view.View; 24 import android.view.animation.AccelerateDecelerateInterpolator; 25 import android.widget.FrameLayout; 26 import android.widget.TextView; 27 28 import androidx.annotation.Nullable; 29 import androidx.constraintlayout.widget.ConstraintLayout; 30 import androidx.recyclerview.widget.LinearLayoutManager; 31 import androidx.recyclerview.widget.RecyclerView; 32 33 import com.android.DeviceAsWebcam.CameraId; 34 import com.android.DeviceAsWebcam.R; 35 36 import java.util.List; 37 import java.util.function.Consumer; 38 39 /** 40 * A class to provide a switch-camera selector view for end users to select a preferred camera. 41 */ 42 public class SwitchCameraSelectorView extends FrameLayout { 43 private List<SelectorListItemData> mSelectorListItemDataList; 44 private View mRootView; 45 private View mRecyclerViewContainerView; 46 private RecyclerView mRecyclerView; 47 private CameraDataAdapter mCameraDataAdapter; 48 private RecyclerView.LayoutManager mLayoutManager; 49 private Consumer<Integer> mVisibilityChangedListener = null; 50 SwitchCameraSelectorView(Context context, AttributeSet attrs)51 public SwitchCameraSelectorView(Context context, AttributeSet attrs) { 52 super(context, attrs); 53 } 54 55 /** 56 * Initializes the switch camera selector view. 57 */ init(LayoutInflater layoutInflater, List<SelectorListItemData> itemDataList)58 public void init(LayoutInflater layoutInflater, List<SelectorListItemData> itemDataList) { 59 removeAllViews(); 60 addView(layoutInflater.inflate(R.layout.switch_camera_selector, null)); 61 mSelectorListItemDataList = itemDataList; 62 63 mRootView = findViewById(R.id.switch_camera_selector_root); 64 mRecyclerViewContainerView = findViewById(R.id.camera_id_list_container); 65 mRecyclerView = findViewById(R.id.camera_id_list_view); 66 findViewById(R.id.list_view_top_item).setEnabled(false); 67 findViewById(R.id.list_view_bottom_item).setEnabled(false); 68 69 mCameraDataAdapter = new CameraDataAdapter(mSelectorListItemDataList, getResources()); 70 mLayoutManager = new LinearLayoutManager(getContext()); 71 mRecyclerView.setLayoutManager(mLayoutManager); 72 mRecyclerView.setAdapter(mCameraDataAdapter); 73 74 mRootView.setOnClickListener(v -> hide()); 75 76 // Hides the camera selector in the beginning 77 hide(); 78 } 79 80 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)81 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 82 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 83 84 // Dynamically calculates the suitable height for the dialog view because the dialog view 85 // is rotated from a portrait orientation layout to landscape. 86 if (mRecyclerViewContainerView != null) { 87 int listItemHeight = getResources().getDimensionPixelSize( 88 R.dimen.list_item_height); 89 int topBottomHeight = getResources().getDimensionPixelSize( 90 R.dimen.list_item_top_bottom_height); 91 int dialogHeight = 92 listItemHeight * mSelectorListItemDataList.size() + topBottomHeight * 2; 93 int margin = getResources().getDimensionPixelSize( 94 R.dimen.camera_selector_top_bottom_margin); 95 int expectedMargin = (getHeight() - dialogHeight) / 2; 96 int minMargin = (getHeight() - getWidth()) / 2 + margin; 97 int finalMargin = Math.max(expectedMargin, minMargin); 98 ConstraintLayout.LayoutParams lp = 99 (ConstraintLayout.LayoutParams) mRecyclerViewContainerView.getLayoutParams(); 100 lp.topMargin = finalMargin; 101 lp.bottomMargin = finalMargin; 102 mRecyclerViewContainerView.setLayoutParams(lp); 103 } 104 } 105 106 /** 107 * Rotates the switch camera selector dialog. 108 */ setRotation(int rotation)109 public void setRotation(int rotation) { 110 ObjectAnimator anim = ObjectAnimator.ofFloat(mRecyclerViewContainerView, 111 /*propertyName=*/"rotation", rotation) 112 .setDuration(300); 113 anim.setInterpolator(new AccelerateDecelerateInterpolator()); 114 anim.start(); 115 } 116 117 /** 118 * Sets a listener to receive the camera selection change events. 119 */ setOnCameraSelectedListener(Consumer<CameraId> listener)120 public void setOnCameraSelectedListener(Consumer<CameraId> listener) { 121 mCameraDataAdapter.setOnCameraSelectedListener(listener); 122 } 123 124 /** 125 * Sets a listener to receive the visibility change events. 126 */ setOnVisibilityChangedListener(@ullable Consumer<Integer> listener)127 public void setOnVisibilityChangedListener(@Nullable Consumer<Integer> listener) { 128 mVisibilityChangedListener = listener; 129 } 130 131 /** 132 * Shows the switch camera selector view. 133 */ show()134 public void show() { 135 mRootView.setAlpha(0f); 136 mRootView.setVisibility(VISIBLE); 137 if (mVisibilityChangedListener != null) { 138 mVisibilityChangedListener.accept(VISIBLE); 139 } 140 141 mRootView.animate() 142 .alpha(1f) 143 .setDuration(300) 144 .setListener(null); 145 } 146 147 /** 148 * Hides the switch camera selector view. 149 */ hide()150 public void hide() { 151 mRootView.setVisibility(GONE); 152 if (mVisibilityChangedListener != null) { 153 mVisibilityChangedListener.accept(GONE); 154 } 155 } 156 157 /** 158 * Updates the selected item. 159 */ updateSelectedItem(CameraId cameraId)160 public void updateSelectedItem(CameraId cameraId) { 161 for (SelectorListItemData itemData : mSelectorListItemDataList) { 162 if (itemData.isHeader) { 163 continue; 164 } 165 boolean oldCheckedState = itemData.isChecked; 166 itemData.isChecked = itemData.cameraInfo.getCameraId().equals(cameraId); 167 if (oldCheckedState != itemData.isChecked) { 168 mCameraDataAdapter.notifyItemChanged( 169 mSelectorListItemDataList.indexOf(itemData)); 170 } 171 if (itemData.isChecked) { 172 mRecyclerView.scrollToPosition(mSelectorListItemDataList.indexOf(itemData)); 173 } 174 } 175 } 176 } 177