1 /*
2  * Copyright 2018 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 androidx.core.view;
18 
19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20 
21 import android.content.Context;
22 import android.util.Log;
23 import android.view.MenuItem;
24 import android.view.SubMenu;
25 import android.view.View;
26 
27 import androidx.annotation.RestrictTo;
28 
29 /**
30  * This class is a mediator for accomplishing a given task, for example sharing a file. It is
31  * responsible for creating a view that performs an action that accomplishes the task. This class
32  * also implements other functions such a performing a default action.
33  *
34  * <p class="note"><strong>Note:</strong> This class is included in the <a
35  * href="{@docRoot}tools/extras/support-library.html">support library</a> for compatibility
36  * with API level 4 and higher. If you're developing your app for API level 14 and higher
37  * <em>only</em>, you should instead use the framework {@link android.view.ActionProvider}
38  * class.</p>
39  *
40  * <p>An ActionProvider can be
41  * optionally specified for a {@link android.view.MenuItem} and in such a case it will be
42  * responsible for
43  * creating the action view that appears in the {@link android.app.ActionBar} as a substitute for
44  * the menu item when the item is displayed as an action item. Also the provider is responsible for
45  * performing a default action if a menu item placed on the overflow menu of the ActionBar is
46  * selected and none of the menu item callbacks has handled the selection. For this case the
47  * provider can also optionally provide a sub-menu for accomplishing the task at hand.
48  *
49  * <p>There are two ways for using an action provider for creating and handling of action views:
50  *
51  * <ul><li> Setting the action provider on a {@link android.view.MenuItem} directly by
52  * calling {@link
53  * androidx.core.view.MenuItemCompat#setActionProvider(android.view.MenuItem, ActionProvider)}.
54  * </li>
55  *
56  * <li>Declaring the action provider in the menu XML resource. For example:
57  *
58  * <pre><code>
59  *   &lt;item android:id="@+id/my_menu_item"
60  *     android:title="@string/my_menu_item_title"
61  *     android:icon="@drawable/my_menu_item_icon"
62  *     android:showAsAction="ifRoom"
63  *     android:actionProviderClass="foo.bar.SomeActionProvider" /&gt;
64  * </code></pre>
65  * </li></ul></p>
66  *
67  * <h3>Creating a custom action provider</h3>
68  *
69  * <p>To create a custom action provider, extend ActionProvider and implement
70  * its callback methods as necessary. In particular, implement the following
71  * methods:</p>
72  *
73  * <dl>
74  * <dt>{@link #ActionProvider ActionProvider()} constructor</dt>
75  * <dd>This constructor is passed the application context. You should
76  * save the context in a member field to use in the other callback methods.</dd>
77  *
78  * <dt>{@link #onCreateActionView onCreateActionView(MenuItem)}</dt>
79  * <dd>The system calls this method when the action provider is created.
80  * You define the action provider's layout through the implementation of this
81  * method. Use the context acquired
82  * from the constructor to instantiate a {@link android.view.LayoutInflater} and
83  * inflate your action provider's layout from an XML resource, then hook up
84  * event listeners for the view's components. For example:
85  *
86  *<pre>
87  * public View onCreateActionView(MenuItem forItem) {
88  *     // Inflate the action provider to be shown on the action bar.
89  *     LayoutInflater layoutInflater = LayoutInflater.from(mContext);
90  *     View providerView =
91  *         layoutInflater.inflate(R.layout.my_action_provider, null);
92  *     ImageButton button =
93  *         (ImageButton) providerView.findViewById(R.id.button);
94  *     button.setOnClickListener(new View.OnClickListener() {
95  *         &#64;Override
96  *         public void onClick(View v) {
97  *             // Do something...
98  *         }
99  *     });
100  *     return providerView;
101  * }</pre>
102  * </dd>
103  *
104  * <dt>{@link #onPerformDefaultAction onPerformDefaultAction()}</dt>
105  * <dd><p>The system calls this method when the user selects a menu item from the action
106  * overflow. The action provider should perform a default action for the
107  * menu item. The system does not call this method if the menu item opens a submenu.</p>
108  *
109  * <p>If your action provider presents a submenu through the
110  * {@link #onPrepareSubMenu onPrepareSubMenu()} callback, the submenu
111  * appears even if the action provider is in the overflow menu.
112  * Thus, the system never calls {@link #onPerformDefaultAction
113  * onPerformDefaultAction()} if there is a submenu.</p>
114  *
115  * <p class="note"> <strong>Note:</strong> An activity or a fragment that
116  * implements <code>onOptionsItemSelected()</code> can override the action
117  * provider's default behavior (unless it uses a submenu) by handling the
118  * item-selected event and returning <code>true</code>. In this case, the
119  * system does not call
120  * {@link #onPerformDefaultAction onPerformDefaultAction()}.</p></dd>
121  * </dl>
122  *
123  *
124  * @see androidx.core.view.MenuItemCompat#setActionProvider(android.view.MenuItem, ActionProvider)
125  * @see androidx.core.view.MenuItemCompat#getActionProvider(android.view.MenuItem)
126  */
127 public abstract class ActionProvider {
128     private static final String TAG = "ActionProvider(support)";
129     private final Context mContext;
130 
131     private SubUiVisibilityListener mSubUiVisibilityListener;
132     private VisibilityListener mVisibilityListener;
133 
134     /**
135      * Creates a new instance.
136      *
137      * @param context Context for accessing resources.
138      */
ActionProvider(Context context)139     public ActionProvider(Context context) {
140         mContext = context;
141     }
142 
143     /**
144      * Gets the context associated with this action provider.
145      */
getContext()146     public Context getContext() {
147         return mContext;
148     }
149 
150     /**
151      * Factory method for creating new action views.
152      *
153      * @return A new action view.
154      */
onCreateActionView()155     public abstract View onCreateActionView();
156 
157     /**
158      * Factory method called by the Android framework to create new action views.
159      * This method returns a new action view for the given MenuItem.
160      *
161      * <p>If your ActionProvider implementation overrides the deprecated no-argument overload
162      * {@link #onCreateActionView()}, overriding this method for devices running API 16 or later
163      * is recommended but optional. The default implementation calls {@link #onCreateActionView()}
164      * for compatibility with applications written for older platform versions.</p>
165      *
166      * @param forItem MenuItem to create the action view for
167      * @return the new action view
168      */
onCreateActionView(MenuItem forItem)169     public View onCreateActionView(MenuItem forItem) {
170         return onCreateActionView();
171     }
172 
173     /**
174      * The result of this method determines whether or not {@link #isVisible()} will be used
175      * by the {@link MenuItem} this ActionProvider is bound to help determine its visibility.
176      *
177      * @return true if this ActionProvider overrides the visibility of the MenuItem
178      *         it is bound to, false otherwise. The default implementation returns false.
179      * @see #isVisible()
180      */
overridesItemVisibility()181     public boolean overridesItemVisibility() {
182         return false;
183     }
184 
185     /**
186      * If {@link #overridesItemVisibility()} returns true, the return value of this method
187      * will help determine the visibility of the {@link MenuItem} this ActionProvider is bound to.
188      *
189      * <p>If the MenuItem's visibility is explicitly set to false by the application,
190      * the MenuItem will not be shown, even if this method returns true.</p>
191      *
192      * @return true if the MenuItem this ActionProvider is bound to is visible, false if
193      *         it is invisible. The default implementation returns true.
194      */
isVisible()195     public boolean isVisible() {
196         return true;
197     }
198 
199     /**
200      * If this ActionProvider is associated with an item in a menu,
201      * refresh the visibility of the item based on {@link #overridesItemVisibility()} and
202      * {@link #isVisible()}. If {@link #overridesItemVisibility()} returns false, this call
203      * will have no effect.
204      */
refreshVisibility()205     public void refreshVisibility() {
206         if (mVisibilityListener != null && overridesItemVisibility()) {
207             mVisibilityListener.onActionProviderVisibilityChanged(isVisible());
208         }
209     }
210 
211     /**
212      * Performs an optional default action.
213      *
214      * <p>For the case of an action provider placed in a menu
215      * item not shown as an action this method is invoked if previous callbacks for processing menu
216      * selection has handled the event.
217      *
218      * <p> A menu item selection is processed in the following order:
219      *
220      * <ul><li>Receiving a call to
221      * {@link android.view.MenuItem.OnMenuItemClickListener#onMenuItemClick
222      * MenuItem.OnMenuItemClickListener.onMenuItemClick}.</li>
223      *
224      * <li>Receiving a call to
225      * {@link android.app.Activity#onOptionsItemSelected(android.view.MenuItem)}
226      * FragmentActivity.onOptionsItemSelected(MenuItem)}
227      * </li>
228      *
229      * <li>Receiving a call to
230      * {@link androidx.fragment.app.Fragment#onOptionsItemSelected(android.view.MenuItem)}
231      * Fragment.onOptionsItemSelected(MenuItem)}</li>
232      *
233      * <li>Launching the {@link android.content.Intent} set via
234      * {@link android.view.MenuItem#setIntent(android.content.Intent)
235      * MenuItem.setIntent(android.content.Intent)}
236      * </li>
237      *
238      * <li>Invoking this method.</li></ul>
239      *
240      * <p>The default implementation does not perform any action and returns false.
241      */
onPerformDefaultAction()242     public boolean onPerformDefaultAction() {
243         return false;
244     }
245 
246     /**
247      * Determines if this ActionProvider has a submenu associated with it.
248      *
249      * <p>Associated submenus will be shown when an action view is not. This provider instance will
250      * receive a call to {@link #onPrepareSubMenu(SubMenu)} after the call to {@link
251      * #onPerformDefaultAction()} and before a submenu is displayed to the user.
252      *
253      * @return true if the item backed by this provider should have an associated submenu
254      */
hasSubMenu()255     public boolean hasSubMenu() {
256         return false;
257     }
258 
259     /**
260      * Called to prepare an associated submenu for the menu item backed by this ActionProvider.
261      *
262      * <p>if {@link #hasSubMenu()} returns true, this method will be called when the menu item is
263      * selected to prepare the submenu for presentation to the user. Apps may use this to create or
264      * alter submenu content right before display.
265      *
266      * @param subMenu Submenu that will be displayed
267      */
onPrepareSubMenu(SubMenu subMenu)268     public void onPrepareSubMenu(SubMenu subMenu) {
269     }
270 
271     /**
272      * Notify the system that the visibility of an action view's sub-UI such as an anchored popup
273      * has changed. This will affect how other system visibility notifications occur.
274      *
275      * @hide Pending future API approval
276      */
277     @RestrictTo(LIBRARY_GROUP)
subUiVisibilityChanged(boolean isVisible)278     public void subUiVisibilityChanged(boolean isVisible) {
279         if (mSubUiVisibilityListener != null) {
280             mSubUiVisibilityListener.onSubUiVisibilityChanged(isVisible);
281         }
282     }
283 
284     /**
285      * @hide Internal use only
286      */
287     @RestrictTo(LIBRARY_GROUP)
setSubUiVisibilityListener(SubUiVisibilityListener listener)288     public void setSubUiVisibilityListener(SubUiVisibilityListener listener) {
289         mSubUiVisibilityListener = listener;
290     }
291 
292     /**
293      * Set a listener to be notified when this ActionProvider's overridden visibility changes.
294      * This should only be used by MenuItem implementations.
295      *
296      * @param listener listener to set
297      */
setVisibilityListener(VisibilityListener listener)298     public void setVisibilityListener(VisibilityListener listener) {
299         if (mVisibilityListener != null && listener != null) {
300             Log.w(TAG, "setVisibilityListener: Setting a new ActionProvider.VisibilityListener " +
301                     "when one is already set. Are you reusing this " + getClass().getSimpleName() +
302                     " instance while it is still in use somewhere else?");
303         }
304         mVisibilityListener = listener;
305     }
306 
307     /**
308      * @hide
309      */
310     @RestrictTo(LIBRARY_GROUP)
reset()311     public void reset() {
312         mVisibilityListener = null;
313         mSubUiVisibilityListener = null;
314     }
315 
316     /**
317      * @hide Internal use only
318      */
319     @RestrictTo(LIBRARY_GROUP)
320     public interface SubUiVisibilityListener {
321 
onSubUiVisibilityChanged(boolean isVisible)322         void onSubUiVisibilityChanged(boolean isVisible);
323     }
324 
325     /**
326      * Listens to changes in visibility as reported by {@link ActionProvider#refreshVisibility()}.
327      *
328      * @see ActionProvider#overridesItemVisibility()
329      * @see ActionProvider#isVisible()
330      */
331     public interface VisibilityListener {
onActionProviderVisibilityChanged(boolean isVisible)332         void onActionProviderVisibilityChanged(boolean isVisible);
333     }
334 }
335