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.google.android.test.windowinsetstests; 18 19 import android.app.Activity; 20 import android.graphics.Insets; 21 import android.os.Bundle; 22 import android.view.View; 23 import android.view.WindowInsets; 24 import android.view.WindowInsets.Type; 25 import android.view.WindowInsetsAnimationControlListener; 26 import android.view.WindowInsetsAnimationController; 27 import android.widget.AdapterView; 28 import android.widget.ArrayAdapter; 29 import android.widget.CompoundButton; 30 import android.widget.SeekBar; 31 import android.widget.Spinner; 32 import android.widget.TextView; 33 import android.widget.ToggleButton; 34 35 public class ControllerActivity extends Activity implements View.OnApplyWindowInsetsListener { 36 37 private ToggleButton mToggleStatus; 38 private SeekBar mSeekStatus; 39 private ToggleButton mToggleNavigation; 40 private SeekBar mSeekNavigation; 41 private ToggleButton mToggleIme; 42 private SeekBar mSeekIme; 43 private TextView mTextControllableInsets; 44 private boolean[] mNotFromUser = {false}; 45 private WindowInsets mLastInsets; 46 47 @Override onCreate(Bundle savedInstanceState)48 protected void onCreate(Bundle savedInstanceState) { 49 super.onCreate(savedInstanceState); 50 setContentView(R.layout.controller_activity); 51 final Spinner spinnerBehavior = findViewById(R.id.spinnerBehavior); 52 ArrayAdapter<CharSequence> adapterBehavior = ArrayAdapter.createFromResource(this, 53 R.array.behaviors, android.R.layout.simple_spinner_item); 54 adapterBehavior.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 55 spinnerBehavior.setAdapter(adapterBehavior); 56 spinnerBehavior.setSelection( 57 spinnerBehavior.getWindowInsetsController().getSystemBarsBehavior()); 58 spinnerBehavior.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 59 @Override 60 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 61 parent.getWindowInsetsController().setSystemBarsBehavior(position); 62 } 63 64 @Override 65 public void onNothingSelected(AdapterView<?> parent) { } 66 }); 67 mToggleStatus = findViewById(R.id.toggleButtonStatus); 68 mToggleStatus.setTag(mNotFromUser); 69 mToggleStatus.setOnCheckedChangeListener(new ToggleListener(Type.statusBars())); 70 mSeekStatus = findViewById(R.id.seekBarStatus); 71 mSeekStatus.setOnSeekBarChangeListener(new SeekBarListener(Type.statusBars())); 72 mToggleNavigation = findViewById(R.id.toggleButtonNavigation); 73 mToggleNavigation.setTag(mNotFromUser); 74 mToggleNavigation.setOnCheckedChangeListener(new ToggleListener(Type.navigationBars())); 75 mSeekNavigation = findViewById(R.id.seekBarNavigation); 76 mSeekNavigation.setOnSeekBarChangeListener(new SeekBarListener(Type.navigationBars())); 77 mToggleIme = findViewById(R.id.toggleButtonIme); 78 mToggleIme.setTag(mNotFromUser); 79 mToggleIme.setOnCheckedChangeListener(new ToggleListener(Type.ime())); 80 mSeekIme = findViewById(R.id.seekBarIme); 81 mSeekIme.setOnSeekBarChangeListener(new SeekBarListener(Type.ime())); 82 mTextControllableInsets = findViewById(R.id.textViewControllableInsets); 83 final View contentView = findViewById(R.id.content); 84 contentView.setOnApplyWindowInsetsListener(this); 85 contentView.getWindowInsetsController().addOnControllableInsetsChangedListener( 86 (c, types) -> mTextControllableInsets.setText( 87 "ControllableInsetsTypes:\n" + insetsTypesToString(types))); 88 } 89 insetsTypesToString(int types)90 private static String insetsTypesToString(int types) { 91 if (types == 0) { 92 return "none"; 93 } 94 final StringBuilder sb = new StringBuilder(); 95 if ((types & Type.statusBars()) != 0) { 96 types &= ~Type.statusBars(); 97 sb.append("statusBars "); 98 } 99 if ((types & Type.navigationBars()) != 0) { 100 types &= ~Type.navigationBars(); 101 sb.append("navigationBars "); 102 } 103 if ((types & Type.captionBar()) != 0) { 104 types &= ~Type.captionBar(); 105 sb.append("captionBar "); 106 } 107 if ((types & Type.ime()) != 0) { 108 types &= ~Type.ime(); 109 sb.append("ime "); 110 } 111 if ((types & Type.systemGestures()) != 0) { 112 types &= ~Type.systemGestures(); 113 sb.append("systemGestures "); 114 } 115 if ((types & Type.mandatorySystemGestures()) != 0) { 116 types &= ~Type.mandatorySystemGestures(); 117 sb.append("mandatorySystemGestures "); 118 } 119 if ((types & Type.tappableElement()) != 0) { 120 types &= ~Type.tappableElement(); 121 sb.append("tappableElement "); 122 } 123 if ((types & Type.displayCutout()) != 0) { 124 types &= ~Type.displayCutout(); 125 sb.append("displayCutout "); 126 } 127 if (types != 0) { 128 sb.append("unknownTypes:").append(types); 129 } 130 return sb.toString(); 131 } 132 133 @Override onApplyWindowInsets(View v, WindowInsets insets)134 public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { 135 mNotFromUser[0] = true; 136 updateWidgets(insets, Type.statusBars(), mToggleStatus, mSeekStatus); 137 updateWidgets(insets, Type.navigationBars(), mToggleNavigation, mSeekNavigation); 138 updateWidgets(insets, Type.ime(), mToggleIme, mSeekIme); 139 mLastInsets = insets; 140 mNotFromUser[0] = false; 141 142 // Prevent triggering system gestures while controlling seek bars. 143 final Insets gestureInsets = insets.getInsets(Type.systemGestures()); 144 v.setPadding(gestureInsets.left, 0, gestureInsets.right, 0); 145 146 return v.onApplyWindowInsets(insets); 147 } 148 updateWidgets(WindowInsets insets, int types, ToggleButton toggle, SeekBar seek)149 private void updateWidgets(WindowInsets insets, int types, ToggleButton toggle, SeekBar seek) { 150 final boolean isVisible = insets.isVisible(types); 151 final boolean wasVisible = mLastInsets != null ? mLastInsets.isVisible(types) : !isVisible; 152 if (isVisible != wasVisible) { 153 toggle.setChecked(isVisible); 154 if (!seek.isPressed()) { 155 seek.setProgress(isVisible ? seek.getMax() : seek.getMin(), true /* animate*/); 156 } 157 } 158 159 } 160 161 private static class ToggleListener implements CompoundButton.OnCheckedChangeListener { 162 163 private final @Type.InsetsType int mTypes; 164 ToggleListener(int types)165 ToggleListener(int types) { 166 mTypes = types; 167 } 168 169 @Override onCheckedChanged(CompoundButton buttonView, boolean isChecked)170 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 171 if (((boolean[]) buttonView.getTag())[0]) { 172 // not from user 173 return; 174 } 175 if (isChecked) { 176 buttonView.getWindowInsetsController().show(mTypes); 177 } else { 178 buttonView.getWindowInsetsController().hide(mTypes); 179 } 180 } 181 } 182 183 private static class SeekBarListener implements SeekBar.OnSeekBarChangeListener { 184 185 private final @Type.InsetsType int mTypes; 186 187 private WindowInsetsAnimationController mController; 188 SeekBarListener(int types)189 SeekBarListener(int types) { 190 mTypes = types; 191 } 192 193 @Override onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)194 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 195 if (mController != null && fromUser) { 196 final int min = seekBar.getMin(); 197 final float fraction = (progress - min) / (float) (seekBar.getMax() - min); 198 final Insets shownInsets = mController.getShownStateInsets(); 199 final Insets hiddenInsets = mController.getHiddenStateInsets(); 200 final Insets currentInsets = Insets.of( 201 (int) (0.5f + fraction * (shownInsets.left - hiddenInsets.left)), 202 (int) (0.5f + fraction * (shownInsets.top - hiddenInsets.top)), 203 (int) (0.5f + fraction * (shownInsets.right - hiddenInsets.right)), 204 (int) (0.5f + fraction * (shownInsets.bottom - hiddenInsets.bottom))); 205 mController.setInsetsAndAlpha(currentInsets, 1f /* alpha */, fraction); 206 } 207 } 208 209 @Override onStartTrackingTouch(SeekBar seekBar)210 public void onStartTrackingTouch(SeekBar seekBar) { 211 if (mController != null) { 212 return; 213 } 214 seekBar.getWindowInsetsController().controlWindowInsetsAnimation(mTypes, 215 -1 /* durationMs */, null /* interpolator */, null /* cancellationSignal */, 216 new WindowInsetsAnimationControlListener() { 217 @Override 218 public void onReady(WindowInsetsAnimationController controller, int types) { 219 mController = controller; 220 if (!seekBar.isPressed()) { 221 onStopTrackingTouch(seekBar); 222 } 223 } 224 225 @Override 226 public void onFinished(WindowInsetsAnimationController controller) { 227 mController = null; 228 } 229 230 @Override 231 public void onCancelled(WindowInsetsAnimationController controller) { 232 mController = null; 233 } 234 }); 235 } 236 237 @Override onStopTrackingTouch(SeekBar seekBar)238 public void onStopTrackingTouch(SeekBar seekBar) { 239 final int min = seekBar.getMin(); 240 final int max = seekBar.getMax(); 241 final boolean shown = (seekBar.getProgress() - min) * 2 > max - min; 242 seekBar.setProgress(shown ? max : min); 243 if (mController != null) { 244 mController.finish(shown); 245 } 246 } 247 } 248 } 249