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         return types == 0 ? "none" : WindowInsets.Type.toString(types);
92     }
93 
94     @Override
onApplyWindowInsets(View v, WindowInsets insets)95     public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
96         mNotFromUser[0] = true;
97         updateWidgets(insets, Type.statusBars(), mToggleStatus, mSeekStatus);
98         updateWidgets(insets, Type.navigationBars(), mToggleNavigation, mSeekNavigation);
99         updateWidgets(insets, Type.ime(), mToggleIme, mSeekIme);
100         mLastInsets = insets;
101         mNotFromUser[0] = false;
102 
103         // Prevent triggering system gestures while controlling seek bars.
104         final Insets gestureInsets =  insets.getInsets(Type.systemGestures());
105         v.setPadding(gestureInsets.left, 0, gestureInsets.right, 0);
106 
107         return v.onApplyWindowInsets(insets);
108     }
109 
updateWidgets(WindowInsets insets, int types, ToggleButton toggle, SeekBar seek)110     private void updateWidgets(WindowInsets insets, int types, ToggleButton toggle, SeekBar seek) {
111         final boolean isVisible = insets.isVisible(types);
112         final boolean wasVisible = mLastInsets != null ? mLastInsets.isVisible(types) : !isVisible;
113         if (isVisible != wasVisible) {
114             toggle.setChecked(isVisible);
115             if (!seek.isPressed()) {
116                 seek.setProgress(isVisible ? seek.getMax() : seek.getMin(), true /* animate*/);
117             }
118         }
119 
120     }
121 
122     private static class ToggleListener implements CompoundButton.OnCheckedChangeListener {
123 
124         private final @Type.InsetsType int mTypes;
125 
ToggleListener(int types)126         ToggleListener(int types) {
127             mTypes = types;
128         }
129 
130         @Override
onCheckedChanged(CompoundButton buttonView, boolean isChecked)131         public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
132             if (((boolean[]) buttonView.getTag())[0]) {
133                 // not from user
134                 return;
135             }
136             if (isChecked) {
137                 buttonView.getWindowInsetsController().show(mTypes);
138             } else {
139                 buttonView.getWindowInsetsController().hide(mTypes);
140             }
141         }
142     }
143 
144     private static class SeekBarListener implements SeekBar.OnSeekBarChangeListener {
145 
146         private final @Type.InsetsType int mTypes;
147 
148         private WindowInsetsAnimationController mController;
149 
SeekBarListener(int types)150         SeekBarListener(int types) {
151             mTypes = types;
152         }
153 
154         @Override
onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)155         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
156             if (mController != null && fromUser) {
157                 final int min = seekBar.getMin();
158                 final float fraction = (progress - min) / (float) (seekBar.getMax() - min);
159                 final Insets shownInsets = mController.getShownStateInsets();
160                 final Insets hiddenInsets = mController.getHiddenStateInsets();
161                 final Insets currentInsets = Insets.of(
162                         (int) (0.5f + fraction * (shownInsets.left - hiddenInsets.left)),
163                         (int) (0.5f + fraction * (shownInsets.top - hiddenInsets.top)),
164                         (int) (0.5f + fraction * (shownInsets.right - hiddenInsets.right)),
165                         (int) (0.5f + fraction * (shownInsets.bottom - hiddenInsets.bottom)));
166                 mController.setInsetsAndAlpha(currentInsets, 1f /* alpha */, fraction);
167             }
168         }
169 
170         @Override
onStartTrackingTouch(SeekBar seekBar)171         public void onStartTrackingTouch(SeekBar seekBar) {
172             if (mController != null) {
173                 return;
174             }
175             seekBar.getWindowInsetsController().controlWindowInsetsAnimation(mTypes,
176                     -1  /* durationMs */, null /* interpolator */, null /* cancellationSignal */,
177                     new WindowInsetsAnimationControlListener() {
178                         @Override
179                         public void onReady(WindowInsetsAnimationController controller, int types) {
180                             mController = controller;
181                             if (!seekBar.isPressed()) {
182                                 onStopTrackingTouch(seekBar);
183                             }
184                         }
185 
186                         @Override
187                         public void onFinished(WindowInsetsAnimationController controller) {
188                             mController = null;
189                         }
190 
191                         @Override
192                         public void onCancelled(WindowInsetsAnimationController controller) {
193                             mController = null;
194                         }
195                     });
196         }
197 
198         @Override
onStopTrackingTouch(SeekBar seekBar)199         public void onStopTrackingTouch(SeekBar seekBar) {
200             final int min = seekBar.getMin();
201             final int max = seekBar.getMax();
202             final boolean shown = (seekBar.getProgress() - min) * 2 > max - min;
203             seekBar.setProgress(shown ? max : min);
204             if (mController != null) {
205                 mController.finish(shown);
206             }
207         }
208     }
209 }
210