----------------[ View, Actions, Intents ]---------------- In class today we saw an example of a very simple "Hello World" application that reacts to a button click to launch into a different screen (i.e., starts a new Activity). That screen offers a TextEdit box, takes input, and reports that input back to the initial Activity as it exits (when the user clicks another button). The initial activity then changes a TextView label to the string entered. Note that clicking the first button replaces the entire content of the app's screen area, i.e., in Android's terms, the entire tree of nested Views in the Window area the app owns. Then, as the editing activity exits, that whole tree is restored. This is how Activities work: they replace the entire hierarchy of visible elements in the app's window area when called, and restore their own when returned to. The code is posted in http://www.cs.dartmouth.edu/~sergey/cs65/examples/Greeter/ For explanations, see comments and https://developer.android.com/training/basics/intents/result.html This directory is flat; you will need to place these files in the appropriate places in the Studio Project). For example, on my system, the paths are AndroidStudioProjects/Greeter/app/src/main/java/com/netfluke/sergey/greeter/ for .java files, AndroidStudioProjects/Greeter/app/src/main/res/layout/ for .xml layout files AndroidStudioProjects/Greeter/app/src/main/AndroidManifest.xml for the manifest file Study the directory structure of your Android Studio projects from the terminal/shell. Ignore the contents of the build/ directory, except for the ./app/build/outputs/apk/debug/ directory, where you will find the APK of the application that is uploaded to your phone or your emulator. We'll talk about dissecting APKs next week. ================[ Windows, Surfaces, Bitmaps ]================ In Android, the visible area of the screen is divided between Windows, which are rectangle areas handled by specific applications. This is where Activities in these applications will draw their hierarchies of Views such as text, images, buttons, list and checkbox widgets, etc., including really complex ones such as embedded Google maps frames. Activities get their rectangle allocation from the WindowManager and fill them with their tree of Views (typically described in XML files such as layout files; Java classes for the Views are created from XML elements). Views draw and redraw themselves into the Surface and Canvas associated with the windows. Good & concise explanations of Android terminology are here: https://stackoverflow.com/questions/9451755/what-is-an-android-window#answers https://stackoverflow.com/questions/4576909/understanding-canvas-and-surface-concepts#answers You can see the View objects that make up Android's home/launcher screen with the Android Device Monitor tool. In the Studio menu bar, click "Tools > Android > Android Device Monitor", then click the DDMS button in the tool that starts up. The tool will close the Studio's adb connection and start its own; this is normal. Select the device, and then use the button tool-tipped "Dump View Hierarchy for UI Automator" (see device-monitor-ddms.png and device-monitor-view-hierarchy.png). Now you can navigate the tree of Views, and find exactly how the screen is laid out. On Monday, we will use the tool called APKtool to extract complete XML layout files from app packages. ================[ Views and Activities ]================ Once Views are measured, laid out, and drawn on the screen, they are ready to receive clicks and other screen-related events (e.g., long clicks, focus changes, screen touches, etc.). Technically, every View occupies a particular rectangle on the screen; a touch or a click in that area gets delivered to the View. A View might not have any callbacks registered for the event. In that case, all you'd get is a little animation of the Button being depressed and released. However, such a button is useless. The click is meant to change something about the state of the app, and so it must be communicated back to the Activity. This is what callbacks do. Callbacks on Views are a part of the famous design pattern called Model-View-Controller. The logic for measuring, laying out, and drawing Views is contained in the View objects; the Activity acts the "controller", which keeps the application states, receives the events via callbacks, queries the application-specific data (the "model"), and updates views with such data. NOTE: Callbacks are registered with the Views, but are defined as methods of the Activity. This means that any variables in the Activity class are in the lexical scope of these callbacks, and are accessible to these functions and shared by them. This is very important: although called in response to events in Views, callbacks run in the context of Activity, and share that context---where the app's state is, and from where they can call to a local or remote database, which implements the model for the application data. ================[ What Views do ]================ Views go through three phases---each one a recursive traversal of the view tree---before they are ready. In each case, each View receives some data and passes some data to its child views. First comes measurement, then figuring out the relative coordinates of child views if any, then, finally, the drawing. These recursive traversals are described in https://developer.android.com/guide/topics/ui/overview.html and https://developer.android.com/reference/android/view/View.html https://developer.android.com/reference/android/view/ViewGroup.html Note that you can define your own views, and overload that methods that are responsible for measuring, drawing, handling touches, etc. ================[ Creating the View tree ]================ So Activities request a Window and fill it in with their tree of Views (one tree for an activity). The typical way an Activity does this is by calling setContentView() and passing it the ID of the XML layout that describes the View elements and how they nest; recall that a Layout is also a View, just one that knows how to position its child Views. So you typically see Activity code like: @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ... } R.layout.activity_main needs some explaining (which I didn't do in class). All resource files in an Android Studio project and all the XML elements that specify the android:id attribute get an integer ID by which the objects created from them can be retrieved via findViewByID() function. These IDs are gathered in an automatically generated file called R.java, and include members called R.layout.* for every layout file, R.id.* for every XML element with an ID, R.string.* for strings in res/values/string.xml and suchlike, R.drawable.* from any images, R.raw.* for binary files like sounds, and so on. You can look for these R.java files in the terminal. Android Studio keeps track of these and will warn you if you try to use an R.* name that has not actually been generated. ================[ Intents ]================ So what kicks off the onCreate() function for an Activity and causes all of the above? Normally, it's an Intent sent by the Launcher app when you click on your app's icon in the Launcher drawer or in a special place on the Launcher screen like the "hot bar" that shows up to 5 app icons (on my phone; your Launcher may be configured differently). There are two kinds of Intents: those that explicitly name the Java class to be launched, and those that don't, but rather specify the Action, the Type, and the Category of the intent. By these three pieces of data, the Intent is _resolved_, i.e., all activities of all the apps on the system that specified intent-filters in their manifests are searched for a match. Implicit intents can launch powerful actions such as taking pictures with the camera, calling phones, sending SMSes, setting alarms, etc. See https://developer.android.com/guide/components/intents-common.html for a long list. If several activities match, a Chooser dialog can be displayed; this is what you see when you are asked which app you'd like to use to complete an action. This happens, e.g., when you have several browsers installed and registered for a "web view" action, and some app wants to display a web page in a browser view, with the ACTION_VIEW (in full, "android.intent.action.VIEW"). That app's intent would then match all of your browsers, and bringing up a Chooser dialog would make sense. The same happens when you have both the Dialer and Skype apps that can make phone calls, and the action is ACTION_CALL (in full, "android.intent.action.CALL"). Details: https://developer.android.com/training/basics/intents/sending.html https://developer.android.com/training/sharing/index.html https://developer.android.com/training/sharing/send.html --sending https://developer.android.com/training/sharing/receive.html --receiving https://developer.android.com/guide/components/intents-filters.html --in depth The Intent sent to start apps from the Launcher is _implicit_: its action is set to "android.intent.action.MAIN", and the category is "android.intent.category.LAUNCHER": ================[ Connecting callbacks ]================ Callbacks of Views can be connected to the Activity that controls those Views either via XML (if the Views are created from an XML description such as a layout file) or from Java code at runtime. The effect is the same: the callback gets connected to a method inside the Activity class. Connecting a Button in XML to a function onButtonClick() [the name of the function can be anything you like; even youPressedOnMeOuch() or some such].