diff --git a/MultiWindowPlayground/.google/packaging.yaml b/MultiWindowPlayground/.google/packaging.yaml new file mode 100644 index 00000000..21fd83dd --- /dev/null +++ b/MultiWindowPlayground/.google/packaging.yaml @@ -0,0 +1,18 @@ + +# GOOGLE SAMPLE PACKAGING DATA +# +# This file is used by Google as part of our samples packaging process. +# End users may safely ignore this file. It has no relevance to other systems. +--- +status: PUBLISHED +technologies: [Android] +categories: [UI] +languages: [Java] +solutions: [Mobile] +github: android/views-widgets +level: INTERMEDIATE +icon: screenshots/icon-web.png +apiRefs: + - android:android.content.Intent + - android:android.app.ActivityOptions +license: apache2 diff --git a/MultiWindowPlayground/Application/build.gradle b/MultiWindowPlayground/Application/build.gradle new file mode 100644 index 00000000..c2a60ee7 --- /dev/null +++ b/MultiWindowPlayground/Application/build.gradle @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.3.0' + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 27 + + defaultConfig { + applicationId "com.android.multiwindowplayground" + minSdkVersion 24 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } +} + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + testCompile 'junit:junit:4.12' + compile 'com.android.support:appcompat-v7:27.0.0' + androidTestCompile 'com.android.support:support-annotations:27.0.0' + androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1' +} diff --git a/MultiWindowPlayground/Application/src/main/AndroidManifest.xml b/MultiWindowPlayground/Application/src/main/AndroidManifest.xml new file mode 100644 index 00000000..25b21bdf --- /dev/null +++ b/MultiWindowPlayground/Application/src/main/AndroidManifest.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/MainActivity.java b/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/MainActivity.java new file mode 100644 index 00000000..bd7b5529 --- /dev/null +++ b/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/MainActivity.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.multiwindowplayground; + +import com.android.multiwindowplayground.activities.AdjacentActivity; +import com.android.multiwindowplayground.activities.BasicActivity; +import com.android.multiwindowplayground.activities.CustomConfigurationChangeActivity; +import com.android.multiwindowplayground.activities.LaunchBoundsActivity; +import com.android.multiwindowplayground.activities.LoggingActivity; +import com.android.multiwindowplayground.activities.MinimumSizeActivity; +import com.android.multiwindowplayground.activities.UnresizableActivity; + +import android.app.ActivityOptions; +import android.content.Intent; +import android.graphics.Rect; +import android.os.Bundle; +import android.util.Log; +import android.view.View; + +public class MainActivity extends LoggingActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + View multiDisabledMessage = findViewById(R.id.warning_multiwindow_disabled); + // Display an additional message if the app is not in multiwindow mode. + if (!isInMultiWindowMode()) { + multiDisabledMessage.setVisibility(View.VISIBLE); + } else { + multiDisabledMessage.setVisibility(View.GONE); + } + } + + public void onStartUnresizableClick(View view) { + Log.d(mLogTag, "** starting UnresizableActivity"); + + /* + * This activity is marked as 'unresizable' in the AndroidManifest. We need to specify the + * FLAG_ACTIVITY_NEW_TASK flag here to launch it into a new task stack, otherwise the + * properties from the root activity would have been inherited (which was here marked as + * resizable by default). + */ + Intent intent = new Intent(this, UnresizableActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + + public void onStartMinimumSizeActivity(View view) { + Log.d(mLogTag, "** starting MinimumSizeActivity"); + + startActivity(new Intent(this, MinimumSizeActivity.class)); + } + + public void onStartAdjacentActivity(View view) { + Log.d(mLogTag, "** starting AdjacentActivity"); + + /* + * Start this activity adjacent to the focused activity (ie. this activity) if possible. + * Note that this flag is just a hint to the system and may be ignored. For example, + * if the activity is launched within the same task, it will be launched on top of the + * previous activity that started the Intent. That's why the Intent.FLAG_ACTIVITY_NEW_TASK + * flag is specified here in the intent - this will start the activity in a new task. + */ + Intent intent = new Intent(this, AdjacentActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT | Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + + public void onStartLaunchBoundsActivity(View view) { + Log.d(mLogTag, "** starting LaunchBoundsActivity"); + + // Define the bounds in which the Activity will be launched into. + Rect bounds = new Rect(500, 300, 100, 0); + + // Set the bounds as an activity option. + ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchBounds(bounds); + + // Start the LaunchBoundsActivity with the specified options + Intent intent = new Intent(this, LaunchBoundsActivity.class); + startActivity(intent, options.toBundle()); + + } + + public void onStartBasicActivity(View view) { + Log.d(mLogTag, "** starting BasicActivity"); + + // Start an Activity with the default options in the 'singleTask' launch mode as defined in + // the AndroidManifest.xml. + startActivity(new Intent(this, BasicActivity.class)); + + } + + public void onStartCustomConfigurationActivity(View view) { + Log.d(mLogTag, "** starting CustomConfigurationChangeActivity"); + + // Start an Activity that handles all configuration changes itself. + startActivity(new Intent(this, CustomConfigurationChangeActivity.class)); + + } +} diff --git a/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/AdjacentActivity.java b/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/AdjacentActivity.java new file mode 100644 index 00000000..cfb915b6 --- /dev/null +++ b/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/AdjacentActivity.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.multiwindowplayground.activities; + +import com.android.multiwindowplayground.R; + +import android.os.Bundle; +import android.view.View; + +/** + * This Activity is to be launched adjacent to another Activity using the {@link + * android.content.Intent#FLAG_ACTIVITY_LAUNCH_ADJACENT}. + * + * @see com.android.multiwindowplayground.MainActivity#onStartAdjacentActivity(View) + */ +public class AdjacentActivity extends LoggingActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_logging); + + setBackgroundColor(R.color.teal); + setDescription(R.string.activity_adjacent_description); + } + +} diff --git a/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/BasicActivity.java b/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/BasicActivity.java new file mode 100644 index 00000000..c40dee22 --- /dev/null +++ b/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/BasicActivity.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.multiwindowplayground.activities; + +import com.android.multiwindowplayground.R; + +import android.os.Bundle; +import android.view.View; + +/** + * This activity is the most basic, simeple use case and is to be launched without any special + * flags + * or settings. + * + * @see com.android.multiwindowplayground.MainActivity#onStartBasicActivity(View) + */ +public class BasicActivity extends LoggingActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_logging); + + // Set the color and description + setDescription(R.string.activity_description_basic); + setBackgroundColor(R.color.gray); + + } +} diff --git a/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/CustomConfigurationChangeActivity.java b/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/CustomConfigurationChangeActivity.java new file mode 100644 index 00000000..32e5233e --- /dev/null +++ b/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/CustomConfigurationChangeActivity.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.multiwindowplayground.activities; + +import com.android.multiwindowplayground.R; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.view.View; + +/** + * This activity handles configuration changes itself. The list of configuration changes that are + * supported is defined in its AndroidManifest definition. Each configuration change triggers a + * call to {@link #onConfigurationChanged(Configuration)}, which is logged in the {@link + * LoggingActivity}. + * + * @see com.android.multiwindowplayground.MainActivity#onStartCustomConfigurationActivity(View) + */ +public class CustomConfigurationChangeActivity extends LoggingActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_logging); + + setBackgroundColor(R.color.cyan); + setDescription(R.string.activity_custom_description); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + /* + Note: The implementation in LoggingActivity logs the output o the new configuration. + This callback is received whenever the configuration is updated, for example when the + size of this Activity is changed. + */ + } +} diff --git a/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/LaunchBoundsActivity.java b/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/LaunchBoundsActivity.java new file mode 100644 index 00000000..80a98f2c --- /dev/null +++ b/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/LaunchBoundsActivity.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.multiwindowplayground.activities; + +import com.android.multiwindowplayground.R; + +import android.os.Bundle; +import android.view.View; + +/** + * In free-form mode, this activity is to be launched within a defined bounds on screen. + * This property is set as part of the Intent that starts this activity. + * + * @see com.android.multiwindowplayground.MainActivity#onStartLaunchBoundsActivity(View) + */ +public class LaunchBoundsActivity extends LoggingActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_logging); + + setBackgroundColor(R.color.lime); + setDescription(R.string.activity_bounds_description); + } + +} diff --git a/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/LoggingActivity.java b/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/LoggingActivity.java new file mode 100644 index 00000000..988a3195 --- /dev/null +++ b/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/LoggingActivity.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.multiwindowplayground.activities; + +import com.android.multiwindowplayground.R; +import com.example.android.common.logger.Log; +import com.example.android.common.logger.LogFragment; +import com.example.android.common.logger.LogWrapper; +import com.example.android.common.logger.MessageOnlyLogFilter; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.PersistableBundle; +import android.support.annotation.ColorRes; +import android.support.annotation.StringRes; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.TextView; + +/** + * Activity that logs all key lifecycle callbacks to {@link Log}. + * Output is also logged to the UI into a {@link LogFragment} through {@link #initializeLogging()} + * and {@link #stopLogging()}. + */ +public abstract class LoggingActivity extends AppCompatActivity { + + protected String mLogTag = getClass().getSimpleName(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log.d(mLogTag, "onCreate"); + + + } + + @Override + public void onPostCreate(Bundle savedInstanceState, PersistableBundle persistentState) { + super.onPostCreate(savedInstanceState, persistentState); + Log.d(mLogTag, "onPostCreate"); + } + + @Override + protected void onPause() { + super.onPause(); + Log.d(mLogTag, "onPause"); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + Log.d(mLogTag, "onDestroy"); + } + + @Override + protected void onResume() { + super.onResume(); + Log.d(mLogTag, "onResume"); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + Log.d(mLogTag, "onConfigurationChanged: " + newConfig.toString()); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + Log.d(mLogTag, "onPostCreate"); + } + + @Override + protected void onStart() { + super.onStart(); + // Start logging to UI. + initializeLogging(); + + Log.d(mLogTag, "onStart"); + } + + @Override + protected void onStop() { + super.onStop(); + // Stop logging to UI when this activity is stopped. + stopLogging(); + + Log.d(mLogTag, "onStop"); + } + + @Override + public void onMultiWindowModeChanged(boolean isInMultiWindowMode) { + super.onMultiWindowModeChanged(isInMultiWindowMode); + + Log.d(mLogTag, "onMultiWindowModeChanged: " + isInMultiWindowMode); + } + + // Logging and UI methods below. + + /** Set up targets to receive log data */ + public void initializeLogging() { + // Using Log, front-end to the logging chain, emulates android.util.log method signatures. + // Wraps Android's native log framework + LogWrapper logWrapper = new LogWrapper(); + Log.setLogNode(logWrapper); + + // Filter strips out everything except the message text. + MessageOnlyLogFilter msgFilter = new MessageOnlyLogFilter(); + logWrapper.setNext(msgFilter); + + // On screen logging via a fragment with a TextView. + LogFragment logFragment = (LogFragment) getSupportFragmentManager() + .findFragmentById(R.id.log_fragment); + msgFilter.setNext(logFragment.getLogView()); + } + + public void stopLogging() { + Log.setLogNode(null); + } + + /** + * Set the description text if a TextView with the id description is available. + */ + protected void setDescription(@StringRes int textId) { + // Set the text and background color + TextView description = (TextView) findViewById(R.id.description); + if (description != null) { + description.setText(textId); + } + } + + /** + * Set the background color for the description text. + */ + protected void setBackgroundColor(@ColorRes int colorId) { + View scrollView = findViewById(R.id.scrollview); + if (scrollView != null) { + scrollView.setBackgroundResource(colorId); + } + } + +} diff --git a/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/MinimumSizeActivity.java b/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/MinimumSizeActivity.java new file mode 100644 index 00000000..a42c379e --- /dev/null +++ b/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/MinimumSizeActivity.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.multiwindowplayground.activities; + +import com.android.multiwindowplayground.R; + +import android.os.Bundle; +import android.view.View; + +/** + * This Activity has a minimum size defined in the AndroidManifeset. + * + * @see com.android.multiwindowplayground.MainActivity#onStartMinimumSizeActivity(View) + */ +public class MinimumSizeActivity extends LoggingActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_logging); + + setBackgroundColor(R.color.pink); + setDescription(R.string.activity_minimum_description); + } + +} diff --git a/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/UnresizableActivity.java b/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/UnresizableActivity.java new file mode 100644 index 00000000..eb2523e9 --- /dev/null +++ b/MultiWindowPlayground/Application/src/main/java/com/android/multiwindowplayground/activities/UnresizableActivity.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.multiwindowplayground.activities; + +import com.android.multiwindowplayground.R; + +import android.os.Bundle; +import android.view.View; + +/** + * This Activity is defined as unresizable in the AndroidManifest. + * This means that this activity is always launched full screen and will not be resized by the + * system. + * + * @see com.android.multiwindowplayground.MainActivity#onStartUnresizableClick(View) + */ +public class UnresizableActivity extends LoggingActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_logging); + + setBackgroundColor(R.color.purple); + setDescription(R.string.activity_description_unresizable); + } + +} diff --git a/MultiWindowPlayground/Application/src/main/java/com/example/android/common/logger/Log.java b/MultiWindowPlayground/Application/src/main/java/com/example/android/common/logger/Log.java new file mode 100755 index 00000000..7c112230 --- /dev/null +++ b/MultiWindowPlayground/Application/src/main/java/com/example/android/common/logger/Log.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +/** + * Helper class for a list (or tree) of LoggerNodes. + * + *

When this is set as the head of the list, + * an instance of it can function as a drop-in replacement for {@link android.util.Log}. + * Most of the methods in this class server only to map a method call in Log to its equivalent + * in LogNode.

+ */ +public class Log { + + // Grabbing the native values from Android's native logging facilities, + // to make for easy migration and interop. + public static final int NONE = -1; + public static final int VERBOSE = android.util.Log.VERBOSE; + public static final int DEBUG = android.util.Log.DEBUG; + public static final int INFO = android.util.Log.INFO; + public static final int WARN = android.util.Log.WARN; + public static final int ERROR = android.util.Log.ERROR; + public static final int ASSERT = android.util.Log.ASSERT; + + // Stores the beginning of the LogNode topology. + private static LogNode mLogNode; + + /** + * Returns the next LogNode in the linked list. + */ + public static LogNode getLogNode() { + return mLogNode; + } + + /** + * Sets the LogNode data will be sent to. + */ + public static void setLogNode(LogNode node) { + mLogNode = node; + } + + /** + * Instructs the LogNode to print the log data provided. Other LogNodes can + * be chained to the end of the LogNode as desired. + * + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging + * facilities + * to extract and print useful information. + */ + public static void println(int priority, String tag, String msg, Throwable tr) { + if (mLogNode != null) { + mLogNode.println(priority, tag, msg, tr); + } + } + + /** + * Instructs the LogNode to print the log data provided. Other LogNodes can + * be chained to the end of the LogNode as desired. + * + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + */ + public static void println(int priority, String tag, String msg) { + println(priority, tag, msg, null); + } + + /** + * Prints a message at VERBOSE priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void v(String tag, String msg, Throwable tr) { + println(VERBOSE, tag, msg, tr); + } + + /** + * Prints a message at VERBOSE priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void v(String tag, String msg) { + v(tag, msg, null); + } + + + /** + * Prints a message at DEBUG priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void d(String tag, String msg, Throwable tr) { + println(DEBUG, tag, msg, tr); + } + + /** + * Prints a message at DEBUG priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void d(String tag, String msg) { + d(tag, msg, null); + } + + /** + * Prints a message at INFO priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void i(String tag, String msg, Throwable tr) { + println(INFO, tag, msg, tr); + } + + /** + * Prints a message at INFO priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void i(String tag, String msg) { + i(tag, msg, null); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void w(String tag, String msg, Throwable tr) { + println(WARN, tag, msg, tr); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void w(String tag, String msg) { + w(tag, msg, null); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void w(String tag, Throwable tr) { + w(tag, null, tr); + } + + /** + * Prints a message at ERROR priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void e(String tag, String msg, Throwable tr) { + println(ERROR, tag, msg, tr); + } + + /** + * Prints a message at ERROR priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void e(String tag, String msg) { + e(tag, msg, null); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void wtf(String tag, String msg, Throwable tr) { + println(ASSERT, tag, msg, tr); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void wtf(String tag, String msg) { + wtf(tag, msg, null); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void wtf(String tag, Throwable tr) { + wtf(tag, null, tr); + } +} diff --git a/MultiWindowPlayground/Application/src/main/java/com/example/android/common/logger/LogFragment.java b/MultiWindowPlayground/Application/src/main/java/com/example/android/common/logger/LogFragment.java new file mode 100755 index 00000000..0e9ea9e4 --- /dev/null +++ b/MultiWindowPlayground/Application/src/main/java/com/example/android/common/logger/LogFragment.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.example.android.common.logger; + +import android.graphics.Typeface; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ScrollView; + +/** + * Simple fraggment which contains a LogView and uses is to output log data it receives + * through the LogNode interface. + */ +public class LogFragment extends Fragment { + + private LogView mLogView; + private ScrollView mScrollView; + + public LogFragment() { + } + + public View inflateViews() { + mScrollView = new ScrollView(getActivity()); + ViewGroup.LayoutParams scrollParams = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + mScrollView.setLayoutParams(scrollParams); + + mLogView = new LogView(getActivity()); + ViewGroup.LayoutParams logParams = new ViewGroup.LayoutParams(scrollParams); + logParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; + mLogView.setTextAppearance(android.R.style.TextAppearance_Material_Medium); + mLogView.setLayoutParams(logParams); + mLogView.setClickable(true); + mLogView.setFocusable(true); + mLogView.setTypeface(Typeface.create("monospace", Typeface.NORMAL)); + + // Want to set padding as 16 dips, setPadding takes pixels. Hooray math! + int paddingDips = 16; + double scale = getResources().getDisplayMetrics().density; + int paddingPixels = (int) ((paddingDips * (scale)) + .5); + mLogView.setPadding(paddingPixels, paddingPixels, paddingPixels, paddingPixels); + mLogView.setCompoundDrawablePadding(paddingPixels); + + mLogView.setGravity(Gravity.BOTTOM); + + mScrollView.addView(mLogView); + return mScrollView; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + View result = inflateViews(); + + mLogView.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + + mScrollView.post(new Runnable() { + @Override + public void run() { + mScrollView + .smoothScrollTo(0, mScrollView.getBottom() + mLogView.getHeight()); + } + }); + } + }); + return result; + } + + public LogView getLogView() { + return mLogView; + } +} \ No newline at end of file diff --git a/MultiWindowPlayground/Application/src/main/java/com/example/android/common/logger/LogNode.java b/MultiWindowPlayground/Application/src/main/java/com/example/android/common/logger/LogNode.java new file mode 100755 index 00000000..7620ca6c --- /dev/null +++ b/MultiWindowPlayground/Application/src/main/java/com/example/android/common/logger/LogNode.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +/** + * Basic interface for a logging system that can output to one or more targets. + * Note that in addition to classes that will output these logs in some format, + * one can also implement this interface over a filter and insert that in the chain, + * such that no targets further down see certain data, or see manipulated forms of the data. + * You could, for instance, write a "ToHtmlLoggerNode" that just converted all the log data + * it received to HTML and sent it along to the next node in the chain, without printing it + * anywhere. + */ +public interface LogNode { + + /** + * Instructs first LogNode in the list to print the log data provided. + * + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging + * facilities + * to extract and print useful information. + */ + public void println(int priority, String tag, String msg, Throwable tr); + +} diff --git a/MultiWindowPlayground/Application/src/main/java/com/example/android/common/logger/LogView.java b/MultiWindowPlayground/Application/src/main/java/com/example/android/common/logger/LogView.java new file mode 100755 index 00000000..b1fb2054 --- /dev/null +++ b/MultiWindowPlayground/Application/src/main/java/com/example/android/common/logger/LogView.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +import android.app.Activity; +import android.content.Context; +import android.util.AttributeSet; +import android.widget.TextView; + +/** + * Simple TextView which is used to output log data received through the LogNode interface. + */ +public class LogView extends TextView implements LogNode { + + public LogView(Context context) { + super(context); + } + + public LogView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public LogView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * Formats the log data and prints it out to the LogView. + * + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging + * facilities + * to extract and print useful information. + */ + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + + String priorityStr = null; + + // For the purposes of this View, we want to print the priority as readable text. + switch (priority) { + case android.util.Log.VERBOSE: + priorityStr = "VERBOSE"; + break; + case android.util.Log.DEBUG: + priorityStr = "DEBUG"; + break; + case android.util.Log.INFO: + priorityStr = "INFO"; + break; + case android.util.Log.WARN: + priorityStr = "WARN"; + break; + case android.util.Log.ERROR: + priorityStr = "ERROR"; + break; + case android.util.Log.ASSERT: + priorityStr = "ASSERT"; + break; + default: + break; + } + + // Handily, the Log class has a facility for converting a stack trace into a usable string. + String exceptionStr = null; + if (tr != null) { + exceptionStr = android.util.Log.getStackTraceString(tr); + } + + // Take the priority, tag, message, and exception, and concatenate as necessary + // into one usable line of text. + final StringBuilder outputBuilder = new StringBuilder(); + + String delimiter = "\t"; + appendIfNotNull(outputBuilder, priorityStr, delimiter); + appendIfNotNull(outputBuilder, tag, delimiter); + appendIfNotNull(outputBuilder, msg, delimiter); + appendIfNotNull(outputBuilder, exceptionStr, delimiter); + + // In case this was originally called from an AsyncTask or some other off-UI thread, + // make sure the update occurs within the UI thread. + ((Activity) getContext()).runOnUiThread((new Thread(new Runnable() { + @Override + public void run() { + // Display the text we just generated within the LogView. + appendToLog(outputBuilder.toString()); + } + }))); + + if (mNext != null) { + mNext.println(priority, tag, msg, tr); + } + } + + public LogNode getNext() { + return mNext; + } + + public void setNext(LogNode node) { + mNext = node; + } + + /** + * Takes a string and adds to it, with a separator, if the bit to be added isn't null. Since + * the logger takes so many arguments that might be null, this method helps cut out some of the + * agonizing tedium of writing the same 3 lines over and over. + * + * @param source StringBuilder containing the text to append to. + * @param addStr The String to append + * @param delimiter The String to separate the source and appended strings. A tab or comma, + * for instance. + * @return The fully concatenated String as a StringBuilder + */ + private StringBuilder appendIfNotNull(StringBuilder source, String addStr, String delimiter) { + if (addStr != null) { + if (addStr.length() == 0) { + delimiter = ""; + } + + return source.append(addStr).append(delimiter); + } + return source; + } + + // The next LogNode in the chain. + LogNode mNext; + + /** Outputs the string as a new line of log data in the LogView. */ + public void appendToLog(String s) { + append("\n" + s); + } + + +} diff --git a/MultiWindowPlayground/Application/src/main/java/com/example/android/common/logger/LogWrapper.java b/MultiWindowPlayground/Application/src/main/java/com/example/android/common/logger/LogWrapper.java new file mode 100755 index 00000000..8594bc47 --- /dev/null +++ b/MultiWindowPlayground/Application/src/main/java/com/example/android/common/logger/LogWrapper.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +import android.util.Log; + +/** + * Helper class which wraps Android's native Log utility in the Logger interface. This way + * normal DDMS output can be one of the many targets receiving and outputting logs simultaneously. + */ +public class LogWrapper implements LogNode { + + // For piping: The next node to receive Log data after this one has done its work. + private LogNode mNext; + + /** + * Returns the next LogNode in the linked list. + */ + public LogNode getNext() { + return mNext; + } + + /** + * Sets the LogNode data will be sent to.. + */ + public void setNext(LogNode node) { + mNext = node; + } + + /** + * Prints data out to the console using Android's native log mechanism. + * + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging + * facilities + * to extract and print useful information. + */ + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + // There actually are log methods that don't take a msg parameter. For now, + // if that's the case, just convert null to the empty string and move on. + String useMsg = msg; + if (useMsg == null) { + useMsg = ""; + } + + // If an exeption was provided, convert that exception to a usable string and attach + // it to the end of the msg method. + if (tr != null) { + msg += "\n" + Log.getStackTraceString(tr); + } + + // This is functionally identical to Log.x(tag, useMsg); + // For instance, if priority were Log.VERBOSE, this would be the same as Log.v(tag, useMsg) + Log.println(priority, tag, useMsg); + + // If this isn't the last node in the chain, move things along. + if (mNext != null) { + mNext.println(priority, tag, msg, tr); + } + } +} diff --git a/MultiWindowPlayground/Application/src/main/java/com/example/android/common/logger/MessageOnlyLogFilter.java b/MultiWindowPlayground/Application/src/main/java/com/example/android/common/logger/MessageOnlyLogFilter.java new file mode 100755 index 00000000..b86e1aef --- /dev/null +++ b/MultiWindowPlayground/Application/src/main/java/com/example/android/common/logger/MessageOnlyLogFilter.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +/** + * Simple {@link LogNode} filter, removes everything except the message. + * Useful for situations like on-screen log output where you don't want a lot of metadata + * displayed, + * just easy-to-read message updates as they're happening. + */ +public class MessageOnlyLogFilter implements LogNode { + + LogNode mNext; + + /** + * Takes the "next" LogNode as a parameter, to simplify chaining. + * + * @param next The next LogNode in the pipeline. + */ + public MessageOnlyLogFilter(LogNode next) { + mNext = next; + } + + public MessageOnlyLogFilter() { + } + + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + if (mNext != null) { + getNext().println(Log.NONE, null, msg, null); + } + } + + /** + * Returns the next LogNode in the chain. + */ + public LogNode getNext() { + return mNext; + } + + /** + * Sets the LogNode data will be sent to.. + */ + public void setNext(LogNode node) { + mNext = node; + } + +} diff --git a/MultiWindowPlayground/Application/src/main/res/layout/activity_logging.xml b/MultiWindowPlayground/Application/src/main/res/layout/activity_logging.xml new file mode 100644 index 00000000..6c6d79b2 --- /dev/null +++ b/MultiWindowPlayground/Application/src/main/res/layout/activity_logging.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/MultiWindowPlayground/Application/src/main/res/layout/activity_main.xml b/MultiWindowPlayground/Application/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..640d9cc9 --- /dev/null +++ b/MultiWindowPlayground/Application/src/main/res/layout/activity_main.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + +