upload android base code part7
245
android/developers/build/build.gradle
Normal file
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
* Copyright 2013 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.
|
||||
*/
|
||||
|
||||
// The SampleGenPlugin source is in the buildSrc directory.
|
||||
import com.example.android.samples.build.SampleGenPlugin
|
||||
apply plugin: SampleGenPlugin
|
||||
|
||||
// Add a preflight task that depends on the "refresh" task that gets
|
||||
// added by the SampleGenPlugin.
|
||||
task preflight {
|
||||
project.afterEvaluate({preflight.dependsOn(project.refresh)})
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '2.10'
|
||||
}
|
||||
|
||||
|
||||
String outPath(String buildType) {
|
||||
/*
|
||||
def repoInfo = "repo info platform/developers/build".execute().text
|
||||
def buildPath = (repoInfo =~ /Mount path: (.*)/)[0][1]
|
||||
*/
|
||||
return "${samplegen.pathToBuild}/out/${buildType}/${samplegen.targetSampleName()}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse a path "IntelliJ-style" by putting dots rather than slashes between
|
||||
* path components that have only one child. So the two paths
|
||||
*
|
||||
* com/example/android/foo/bar.java
|
||||
* com/example/android/bar/foo.java
|
||||
*
|
||||
* Become
|
||||
* com.example.android/foo/bar.java
|
||||
* com.example.android/bar/foo.java
|
||||
*
|
||||
* @param path
|
||||
* @param roots
|
||||
* @return
|
||||
*/
|
||||
Map<String,String> collapsePaths(FileTree path, List<String> roots) {
|
||||
Map result = new HashMap<String,String>();
|
||||
|
||||
println ("******************** Collapse *************************")
|
||||
|
||||
path.visit { FileVisitDetails f ->
|
||||
if (f.isDirectory()) return;
|
||||
StringBuilder collapsedPath = new StringBuilder("${f.name}");
|
||||
File current = f.file;
|
||||
|
||||
//
|
||||
// Starting at this file, walk back to the root of the path and
|
||||
// substitute dots for any directory that has only one child.
|
||||
//
|
||||
|
||||
// Don't substitute a dot for the separator between the end of the
|
||||
// path and the filename, even if there's only one file in the directory.
|
||||
if (!f.isDirectory()) {
|
||||
current = current.parentFile;
|
||||
collapsedPath.insert(0, "${current.name}/")
|
||||
}
|
||||
|
||||
// For everything else, use a dot if there's only one child and
|
||||
// a slash otherwise. Filter out the root paths, too--we only want
|
||||
// the relative path. But wait, Groovy/Gradle is capricious and
|
||||
// won't return the proper value from a call to roots.contains(String)!
|
||||
// I'm using roots.sum here instead of tracking down why a list of
|
||||
// strings can't return true from contains() when given a string that
|
||||
// it quite obviously does contain.
|
||||
current = current.parentFile;
|
||||
while((current != null)
|
||||
&& (roots.sum {String r-> return r.equals(current.absolutePath) ? 1 : 0 } == 0)) {
|
||||
|
||||
char separator = current.list().length > 1 ? '/' : '.';
|
||||
collapsedPath.insert(0, "${current.name}${separator}");
|
||||
current = current.parentFile;
|
||||
}
|
||||
result.put(f.file.path, collapsedPath.toString());
|
||||
}
|
||||
|
||||
println ("******************** Collapse results *********************")
|
||||
|
||||
result.each {entry -> println("- ${entry}");}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
task emitAnt(type:Copy) {
|
||||
def outputPath = outPath("ant");
|
||||
def inputPath = "${project.projectDir}/${samplegen.targetSampleModule()}"
|
||||
into outputPath
|
||||
includeEmptyDirs
|
||||
["main", "common", "template"].each { input ->
|
||||
[[ "java", "src"], ["res", "res"]].each { filetype ->
|
||||
def srcPath = "${inputPath}/src/${input}/${filetype[0]}"
|
||||
into("${filetype[1]}") {
|
||||
from(srcPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
from("${inputPath}/src/main") { include "AndroidManifest.xml" }
|
||||
from("${inputPath}/src/template") { include "project.properties" }
|
||||
}
|
||||
|
||||
task emitGradle(type:Copy) {
|
||||
dependsOn(preflight)
|
||||
def outputPath = outPath("gradle")
|
||||
def inputPath = "${project.projectDir}"
|
||||
// Copy entire sample into output -- since it's already in Gradle format, we'll explicitly exclude content that
|
||||
// doesn't belong here.
|
||||
into outputPath
|
||||
from("${inputPath}") {
|
||||
// Paths to exclude from output
|
||||
exclude ".gradle"
|
||||
exclude "_index.jd"
|
||||
exclude "bin"
|
||||
exclude "buildSrc"
|
||||
exclude "local.properties"
|
||||
exclude "template-params.xml"
|
||||
exclude "*.iml"
|
||||
exclude "**/.idea"
|
||||
exclude "**/build"
|
||||
exclude "**/proguard-project.txt"
|
||||
exclude "${samplegen.targetSampleModule()}/**/README*.txt"
|
||||
exclude "**/README-*.txt"
|
||||
|
||||
// src directory needs to be consolidated, will be done in next section
|
||||
exclude "${samplegen.targetSampleModule()}/src/"
|
||||
}
|
||||
|
||||
// Consolidate source directories
|
||||
["main", "common", "template"].each { input ->
|
||||
["java", "res", "assets", "rs"].each { filetype ->
|
||||
def srcPath = "${inputPath}/${samplegen.targetSampleModule()}/src/${input}/${filetype}"
|
||||
into("${samplegen.targetSampleModule()}/src/main/${filetype}") {
|
||||
from(srcPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy AndroidManifest.xml
|
||||
into ("${samplegen.targetSampleModule()}/src/main") {
|
||||
from("${inputPath}/${samplegen.targetSampleModule()}/src/main/AndroidManifest.xml")
|
||||
}
|
||||
|
||||
// Remove BEGIN_EXCLUDE/END_EXCLUDE blocks from source files
|
||||
eachFile { file ->
|
||||
if (file.name.endsWith(".gradle") || file.name.endsWith(".java")) {
|
||||
// TODO(trevorjohns): Outputs a blank newline for each filtered line. Replace with java.io.FilterReader impl.
|
||||
boolean outputLines = true;
|
||||
def removeExcludeBlocksFilter = { line ->
|
||||
if (line ==~ /\/\/ BEGIN_EXCLUDE/) {
|
||||
outputLines = false;
|
||||
} else if (line ==~ /\/\/ END_EXCLUDE/) {
|
||||
outputLines = true;
|
||||
} else if (outputLines) {
|
||||
return line;
|
||||
}
|
||||
return ""
|
||||
}
|
||||
filter(removeExcludeBlocksFilter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task emitBrowseable(type:Copy) {
|
||||
def outputPathRoot = outPath("browseable")
|
||||
def modules = project.childProjects.keySet()
|
||||
def hasMultipleModules = modules.size() > 1
|
||||
println "---------------- modules found in sample: ${modules}"
|
||||
into outputPathRoot
|
||||
from("${project.projectDir}/_index.jd")
|
||||
|
||||
modules.each { moduleName ->
|
||||
// For single module samples (default), we emit the module contents
|
||||
// directly to the root of the browseable sample:
|
||||
def outputPath = "."
|
||||
if (hasMultipleModules) {
|
||||
// For multi module samples, we need an extra directory level
|
||||
// to separate modules:
|
||||
outputPath = "${moduleName}"
|
||||
}
|
||||
println "\n---------------- processing MODULE ${moduleName} to outputPath ${outputPath}"
|
||||
def inputPath = "${project.projectDir}/${moduleName}"
|
||||
|
||||
def srcDirs = ["main", "common", "template"].collect {input -> "${inputPath}/src/${input}" };
|
||||
def javaDirs = srcDirs.collect { input -> "${input}/java"}
|
||||
FileTree javaTree = null;
|
||||
javaDirs.each { dir ->
|
||||
FileTree tree = project.fileTree("${dir}")
|
||||
javaTree = (javaTree == null) ? tree : javaTree.plus(tree)}
|
||||
Map collapsedPaths = collapsePaths(javaTree, javaDirs)
|
||||
|
||||
srcDirs.each { srcPath ->
|
||||
print "** Copying source ${srcPath}...";
|
||||
duplicatesStrategy = 'fail'
|
||||
into("${outputPath}/src") {
|
||||
def javaPath = "${srcPath}/java";
|
||||
from(javaPath)
|
||||
include(["**/*.java", "**/*.xml"])
|
||||
eachFile { FileCopyDetails fcd ->
|
||||
if (fcd.file.isFile()) {
|
||||
def filename = fcd.name;
|
||||
String collapsed = collapsedPaths.get(fcd.file.path);
|
||||
fcd.path = "${outputPath}/src/${collapsed}";
|
||||
} else {fcd.exclude()}
|
||||
}
|
||||
println "done"
|
||||
}
|
||||
into("${outputPath}/res") {
|
||||
from("${srcPath}/res")
|
||||
}
|
||||
into("${outputPath}/src/rs") {
|
||||
from("${srcPath}/rs")
|
||||
}
|
||||
into("${outputPath}") {from("${srcPath}/AndroidManifest.xml")}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task emitGradleZip(dependsOn: [emitBrowseable, emitGradle], type:Zip) {
|
||||
def outputPath = "${samplegen.pathToBuild}/out/browseable"
|
||||
def folderName = "${samplegen.targetSampleName()}"
|
||||
archiveName = "${samplegen.targetSampleName()}.zip"
|
||||
def inputPath = outPath("gradle")
|
||||
from inputPath
|
||||
into folderName
|
||||
include "**"
|
||||
def outDir = project.file(outputPath)
|
||||
destinationDir = outDir
|
||||
}
|
13
android/developers/build/build.iml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.path="$USER_HOME$/src/android/developers-dev/developers/samples/android/common/build" external.system.id="GRADLE" type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
|
11
android/developers/build/buildSrc/build.gradle
Normal file
|
@ -0,0 +1,11 @@
|
|||
apply plugin: 'groovy'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.freemarker:freemarker:2.3.20'
|
||||
compile gradleApi()
|
||||
compile localGroovy()
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* Copyright 2013 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.samples.build
|
||||
|
||||
import freemarker.cache.FileTemplateLoader
|
||||
import freemarker.cache.MultiTemplateLoader
|
||||
import freemarker.cache.TemplateLoader
|
||||
import freemarker.template.Configuration
|
||||
import freemarker.template.DefaultObjectWrapper
|
||||
import freemarker.template.Template
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.file.FileVisitDetails
|
||||
import org.gradle.api.tasks.InputDirectory
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
import org.gradle.api.tasks.SourceTask
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
|
||||
class ApplyTemplates extends SourceTask {
|
||||
/**
|
||||
* Freemarker context object
|
||||
*/
|
||||
def Configuration cfg = new freemarker.template.Configuration()
|
||||
|
||||
/**
|
||||
* The root directory for output files. All output file paths
|
||||
* are assumed to be relative to this root.
|
||||
*/
|
||||
@OutputDirectory
|
||||
public outputDir = project.projectDir
|
||||
|
||||
/**
|
||||
* Include directory. The templates in this directory will not be
|
||||
* processed directly, but will be accessible to other templates
|
||||
* via the <#include> directive.
|
||||
*/
|
||||
def include = project.file("$project.projectDir/templates/include")
|
||||
|
||||
/**
|
||||
* List of file extensions that indicate a file to be processed, rather
|
||||
* than simply copied.
|
||||
*/
|
||||
def extensionsToProcess = ['ftl']
|
||||
|
||||
/**
|
||||
* List of file extensions that should be completely ignored by this
|
||||
* task. File extensions that appear in neither this list nor the list
|
||||
* specified by {@link #extensionsToProcess} are copied into the destination
|
||||
* without processing.
|
||||
*/
|
||||
def extensionsToIgnore = ['ftli']
|
||||
|
||||
/**
|
||||
* A String -> String closure that transforms a (relative) input path into a
|
||||
* (relative) output path. This closure is responsible for any alterations to
|
||||
* the output path, including pathname substitution and extension removal.
|
||||
*/
|
||||
Closure<String> filenameTransform
|
||||
|
||||
/**
|
||||
* The hash which will be passed to the freemarker template engine. This hash
|
||||
* is used by the freemarker script as input data.
|
||||
* The hash should contain a key named "meta". The template processor will add
|
||||
* processing data to this key.
|
||||
*/
|
||||
def parameters
|
||||
|
||||
/**
|
||||
* The main action for this task. Visits each file in the source directories and
|
||||
* either processes, copies, or ignores it. The action taken for each file depends
|
||||
* on the contents of {@link #extensionsToProcess} and {@link #extensionsToIgnore}.
|
||||
*/
|
||||
@TaskAction
|
||||
def applyTemplate() {
|
||||
// Create a list of Freemarker template loaders based on the
|
||||
// source tree(s) of this task. The loader list establishes a virtual
|
||||
// file system for freemarker templates; the template language can
|
||||
// load files, and each load request will have its path resolved
|
||||
// against this set of loaders.
|
||||
println "Gathering template load locations:"
|
||||
def List loaders = []
|
||||
source.asFileTrees.each {
|
||||
src ->
|
||||
println " ${src.dir}"
|
||||
loaders.add(0, new FileTemplateLoader(project.file(src.dir)))
|
||||
}
|
||||
|
||||
// Add the include path(s) to the list of loaders.
|
||||
println "Gathering template include locations:"
|
||||
include = project.fileTree(include)
|
||||
include.asFileTrees.each {
|
||||
inc ->
|
||||
println " ${inc.dir}"
|
||||
loaders.add(0, new FileTemplateLoader(project.file(inc.dir)))
|
||||
}
|
||||
// Add the loaders to the freemarker config
|
||||
cfg.setTemplateLoader(new MultiTemplateLoader(loaders.toArray(new TemplateLoader[1])))
|
||||
|
||||
// Set the wrapper that will be used to convert the template parameters hash into
|
||||
// the internal freemarker data model. The default wrapper is capable of handling a
|
||||
// mix of POJOs/POGOs and XML nodes, so we'll use that.
|
||||
cfg.setObjectWrapper(new DefaultObjectWrapper())
|
||||
|
||||
// This is very much like setting the target SDK level in Android.
|
||||
cfg.setIncompatibleEnhancements("2.3.20")
|
||||
|
||||
// Add an implicit <#include 'common.ftl' to the top of every file.
|
||||
// TODO: should probably be a parameter instead of hardcoded like this.
|
||||
cfg.addAutoInclude('common.ftl')
|
||||
|
||||
// Visit every file in the source tree(s)
|
||||
def processTree = source.getAsFileTree()
|
||||
processTree.visit {
|
||||
FileVisitDetails input ->
|
||||
def inputFile = input.getRelativePath().toString()
|
||||
def outputFile = input.getRelativePath().getFile(project.file(outputDir))
|
||||
// Get the input and output files, and make sure the output path exists
|
||||
def renamedOutput = filenameTransform(outputFile.toString())
|
||||
outputFile = project.file(renamedOutput)
|
||||
|
||||
if (input.directory){
|
||||
// create the output directory. This probably will have already been
|
||||
// created as part of processing the files *in* the directory, but
|
||||
// do it here anyway to support empty directories.
|
||||
outputFile.mkdirs()
|
||||
} else {
|
||||
// We may or may not see the directory before we see the files
|
||||
// in that directory, so create it here
|
||||
outputFile.parentFile.mkdirs()
|
||||
|
||||
// Check the input file extension against the process/ignore list
|
||||
def extension = "NONE"
|
||||
def extensionPattern = ~/.*\.(\w*)$/
|
||||
def extensionMatch = extensionPattern.matcher(inputFile)
|
||||
if (extensionMatch.matches()) {
|
||||
extension = extensionMatch[0][1]
|
||||
}
|
||||
// If the extension is in the process list, put the input through freemarker
|
||||
if (extensionsToProcess.contains(extension)){
|
||||
print '[freemarker] PROCESS: '
|
||||
println "$inputFile -> $outputFile"
|
||||
|
||||
try {
|
||||
def Template tpl = this.cfg.getTemplate(inputFile)
|
||||
def FileWriter out = new FileWriter(outputFile)
|
||||
|
||||
// Add the output file path to parameters.meta so that the freemarker
|
||||
// script can access it.
|
||||
parameters.meta.put("outputFile", "${outputFile}")
|
||||
tpl.process(parameters, out)
|
||||
} catch (e) {
|
||||
println e.message
|
||||
throw new GradleException("Error processing ${inputFile}: ${e.message}")
|
||||
}
|
||||
} else if (!extensionsToIgnore.contains(extension)) {
|
||||
// if it's not processed and not ignored, then it must be copied.
|
||||
print '[freemarker] COPY: '
|
||||
println "$inputFile -> $outputFile"
|
||||
input.copyTo(outputFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright 2013 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.samples.build
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.tasks.GradleBuild
|
||||
/**
|
||||
* Plugin to expose build rules for sample generation and packaging.
|
||||
*/
|
||||
class SampleGenPlugin implements Plugin {
|
||||
|
||||
/**
|
||||
* Creates a new sample generator task based on the supplied sources.
|
||||
*
|
||||
* @param name Name of the new task
|
||||
* @param sources Source tree that this task should process
|
||||
*/
|
||||
void createTask(
|
||||
Project project,
|
||||
String name,
|
||||
SampleGenProperties props,
|
||||
def sources,
|
||||
def destination) {
|
||||
project.task ([type:ApplyTemplates], name, {
|
||||
sources.each { tree ->
|
||||
source += tree
|
||||
}
|
||||
outputDir = destination
|
||||
include = props.templatesInclude()
|
||||
filenameTransform = {s -> props.getOutputForInput(s)}
|
||||
parameters = props.templateParams()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void apply(project) {
|
||||
project.extensions.create("samplegen", SampleGenProperties)
|
||||
project.samplegen.project = project
|
||||
SampleGenProperties samplegen = project.samplegen
|
||||
project.task('create') {
|
||||
if (project.gradle.startParameter.taskNames.contains('create')) {
|
||||
samplegen.getCreationProperties()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
project.task('refresh') {
|
||||
samplegen.getRefreshProperties()
|
||||
}
|
||||
|
||||
project.afterEvaluate({
|
||||
createTask(project,
|
||||
'processTemplates',
|
||||
samplegen,
|
||||
samplegen.templates(),
|
||||
samplegen.targetProjectPath)
|
||||
createTask(project,
|
||||
'processCommon',
|
||||
samplegen,
|
||||
samplegen.common(),
|
||||
samplegen.targetCommonPath())
|
||||
|
||||
|
||||
project.task([type: GradleBuild], 'bootstrap', {
|
||||
buildFile = "${samplegen.targetProjectPath}/build.gradle"
|
||||
dir = samplegen.targetProjectPath
|
||||
tasks = ["refresh"]
|
||||
})
|
||||
project.bootstrap.dependsOn(project.processTemplates)
|
||||
project.bootstrap.dependsOn(project.processCommon)
|
||||
project.create.dependsOn(project.bootstrap)
|
||||
|
||||
project.refresh.dependsOn(project.processTemplates)
|
||||
project.refresh.dependsOn(project.processCommon)
|
||||
|
||||
// People get nervous when they see a task with no actions, so...
|
||||
project.create << {println "Project creation finished."}
|
||||
project.refresh << {println "Project refresh finished."}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,320 @@
|
|||
/*
|
||||
* Copyright 2013 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.samples.build
|
||||
|
||||
import freemarker.ext.dom.NodeModel
|
||||
import groovy.transform.Canonical
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.file.FileTree
|
||||
|
||||
/**
|
||||
* Gradle extension that holds properties for sample generation.
|
||||
*
|
||||
* The sample generator needs a number of properties whose values can be
|
||||
* inferred by convention from a smaller number of initial properties.
|
||||
* This class defines fields for the initial properties, and getter
|
||||
* methods for the inferred properties. It also defines a small number
|
||||
* of convenience methods for setting up template-generation tasks.
|
||||
*/
|
||||
@Canonical
|
||||
class SampleGenProperties {
|
||||
/**
|
||||
* The Gradle project that this extension is being applied to.
|
||||
*/
|
||||
Project project
|
||||
|
||||
/**
|
||||
* Directory where the top-level sample project lives
|
||||
*/
|
||||
def targetProjectPath
|
||||
|
||||
/**
|
||||
* Relative path to samples/common directory
|
||||
*/
|
||||
def pathToSamplesCommon
|
||||
|
||||
/**
|
||||
* Relative path to build directory (platform/developers/build)
|
||||
*/
|
||||
def pathToBuild
|
||||
|
||||
/**
|
||||
* Java package name for the root package of this sample.
|
||||
*/
|
||||
String targetSamplePackage
|
||||
|
||||
/**
|
||||
*
|
||||
* @return The path to the sample project (as opposed to the top-level project, which
|
||||
* what is that even for anyway?)
|
||||
*/
|
||||
String targetSamplePath() {
|
||||
return "${targetProjectPath}/${targetSampleModule()}"
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @return The path that contains common files -- can be cleaned without harming
|
||||
* the sample
|
||||
*/
|
||||
String targetCommonPath() {
|
||||
return "${targetSamplePath()}/src/common/java/com/example/android/common"
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return The path that contains template files -- can be cleaned without harming
|
||||
* the sample
|
||||
*/
|
||||
String targetTemplatePath() {
|
||||
return "${targetSamplePath()}/src/template"
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of this sample (and also of the corresponding .iml file)
|
||||
*/
|
||||
String targetSampleName() {
|
||||
return project.file(targetProjectPath).getName()
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the main module in the sample project
|
||||
*/
|
||||
String targetSampleModule() {
|
||||
return "Application"
|
||||
}
|
||||
|
||||
/**
|
||||
* The path to the template parameters file
|
||||
*/
|
||||
String templateXml() {
|
||||
return "${targetProjectPath}/template-params.xml"
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a package name into a java-style OS dependent path
|
||||
* @param pkg cccc
|
||||
* @return The java-style path to the package's code
|
||||
*/
|
||||
String packageAsPath(String pkg) {
|
||||
return pkg.replaceAll(/\./, File.separator)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a path into a java-style package name
|
||||
* @param path The java-style path to the package's code
|
||||
* @return Name of the package to transform
|
||||
*/
|
||||
String pathAsPackage(String path) {
|
||||
return path.replaceAll(File.separator, /\./)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the common/build/templates directory
|
||||
*/
|
||||
String templatesRoot() {
|
||||
return "${targetProjectPath}/${pathToBuild}/templates"
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the path to common/src/java
|
||||
*/
|
||||
String commonSourceRoot() {
|
||||
return "${targetProjectPath}/${pathToSamplesCommon}/src/java/com/example/android/common"
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the template include directory
|
||||
*/
|
||||
String templatesInclude() {
|
||||
return "${templatesRoot()}/include"
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the output file that will be generated for a particular
|
||||
* input, by replacing generic pathnames with project-specific pathnames
|
||||
* and dropping the .ftl extension from freemarker files.
|
||||
*
|
||||
* @param relativeInputPath Input file as a relative path from the template directory
|
||||
* @return Relative output file path
|
||||
*/
|
||||
String getOutputForInput(String relativeInputPath) {
|
||||
String outputPath = relativeInputPath
|
||||
outputPath = outputPath.replaceAll('_PROJECT_', targetSampleName())
|
||||
outputPath = outputPath.replaceAll('_MODULE_', targetSampleModule())
|
||||
outputPath = outputPath.replaceAll('_PACKAGE_', packageAsPath(targetSamplePackage))
|
||||
|
||||
// This is kind of a hack; IntelliJ picks up any and all subdirectories named .idea, so
|
||||
// named them ._IDE_ instead. TODO: remove when generating .idea projects is no longer necessary.
|
||||
outputPath = outputPath.replaceAll('_IDE_', "idea")
|
||||
outputPath = outputPath.replaceAll(/\.ftl$/, '')
|
||||
|
||||
// Any file beginning with a dot won't get picked up, so rename them as necessary here.
|
||||
outputPath = outputPath.replaceAll('gitignore', '.gitignore')
|
||||
return outputPath
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tree(s) where the templates to be processed live. The template
|
||||
* input paths that are passed to
|
||||
* {@link SampleGenProperties#getOutputForInput(java.lang.String) getOutputForInput}
|
||||
* are relative to the dir element in each tree.
|
||||
*/
|
||||
FileTree[] templates() {
|
||||
def result = []
|
||||
def xmlFile = project.file(templateXml())
|
||||
if (xmlFile.exists()) {
|
||||
def xml = new XmlSlurper().parse(xmlFile)
|
||||
xml.template.each { template ->
|
||||
result.add(project.fileTree(dir: "${templatesRoot()}/${template.@src}"))
|
||||
}
|
||||
} else {
|
||||
result.add(project.fileTree(dir: "${templatesRoot()}/create"))
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path(s) of the common directories to copy over to the sample project.
|
||||
*/
|
||||
FileTree[] common() {
|
||||
def result = []
|
||||
def xmlFile = project.file(templateXml())
|
||||
if (xmlFile.exists()) {
|
||||
def xml = new XmlSlurper().parse(xmlFile)
|
||||
xml.common.each { common ->
|
||||
println "Adding common/${common.@src} from ${commonSourceRoot()}"
|
||||
result.add(project.fileTree (
|
||||
dir: "${commonSourceRoot()}",
|
||||
include: "${common.@src}/**/*"
|
||||
))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hash to supply to the freemarker template processor.
|
||||
* This is loaded from the file specified by {@link SampleGenProperties#templateXml()}
|
||||
* if such a file exists, or synthesized with some default parameters if it does not.
|
||||
* In addition, some data about the current project is added to the "meta" key of the
|
||||
* hash.
|
||||
*
|
||||
* @return The hash to supply to freemarker
|
||||
*/
|
||||
Map templateParams() {
|
||||
Map result = new HashMap();
|
||||
|
||||
def xmlFile = project.file(templateXml())
|
||||
if (xmlFile.exists()) {
|
||||
// Parse the xml into Freemarker's DOM structure
|
||||
def params = freemarker.ext.dom.NodeModel.parse(xmlFile)
|
||||
|
||||
// Move to the <sample> node and stuff that in our map
|
||||
def sampleNode = (NodeModel)params.exec(['/sample'])
|
||||
result.put("sample", sampleNode)
|
||||
} else {
|
||||
// Fake data for use on creation
|
||||
result.put("sample", [
|
||||
name:targetSampleName(),
|
||||
package:targetSamplePackage,
|
||||
minSdk:4
|
||||
])
|
||||
}
|
||||
|
||||
// Extra data that some templates find useful
|
||||
result.put("meta", [
|
||||
root: targetProjectPath,
|
||||
module: targetSampleModule(),
|
||||
common: pathToSamplesCommon,
|
||||
build: pathToBuild,
|
||||
])
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Generate default values for properties that can be inferred from an existing
|
||||
* generated project, unless those properties have already been
|
||||
* explicitly specified.
|
||||
*/
|
||||
void getRefreshProperties() {
|
||||
if (!this.targetProjectPath) {
|
||||
this.targetProjectPath = project.projectDir
|
||||
}
|
||||
def xmlFile = project.file(templateXml())
|
||||
if (xmlFile.exists()) {
|
||||
println "Template XML: $xmlFile"
|
||||
def xml = new XmlSlurper().parse(xmlFile)
|
||||
this.targetSamplePackage = xml.package.toString()
|
||||
println "Target Package: $targetSamplePackage"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate default values for creation properties, unless those properties
|
||||
* have already been explicitly specified. This method will attempt to get
|
||||
* these properties interactively from the user if necessary.
|
||||
*/
|
||||
void getCreationProperties() {
|
||||
def calledFrom = project.hasProperty('calledFrom') ? new File(project.calledFrom)
|
||||
: project.projectDir
|
||||
calledFrom = calledFrom.getCanonicalPath()
|
||||
println('\n\n\nReady to create project...')
|
||||
|
||||
if (project.hasProperty('pathToSamplesCommon')) {
|
||||
this.pathToSamplesCommon = project.pathToSamplesCommon
|
||||
} else {
|
||||
throw new GradleException (
|
||||
'create task requires project property pathToSamplesCommon')
|
||||
}
|
||||
|
||||
|
||||
if (project.hasProperty('pathToBuild')) {
|
||||
this.pathToBuild = project.pathToBuild
|
||||
} else {
|
||||
throw new GradleException ('create task requires project property pathToBuild')
|
||||
}
|
||||
|
||||
if (!this.targetProjectPath) {
|
||||
if (project.hasProperty('out')) {
|
||||
this.targetProjectPath = project.out
|
||||
} else {
|
||||
this.targetProjectPath = System.console().readLine(
|
||||
"\noutput directory [$calledFrom]:")
|
||||
if (this.targetProjectPath.length() <= 0) {
|
||||
this.targetProjectPath = calledFrom
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.targetSamplePackage) {
|
||||
def defaultPackage = "com.example.android." +
|
||||
this.targetSampleName().toLowerCase()
|
||||
this.targetSamplePackage = System.console().readLine(
|
||||
"\nsample package name[$defaultPackage]:")
|
||||
if (this.targetSamplePackage.length() <= 0) {
|
||||
this.targetSamplePackage = defaultPackage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
12
android/developers/build/buildSrc/src/main/main.iml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/groovy" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
|
358
android/developers/build/github.sh
Executable file
|
@ -0,0 +1,358 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## GitHub Upload+Update Script (V2, combined) for DevPlat Samples
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
update=true
|
||||
upload=true
|
||||
deleteTemp=true
|
||||
useAllSamples=true
|
||||
allSamples=()
|
||||
token=
|
||||
|
||||
## Generates a random 32 character alphaneumeric string to use as a post script
|
||||
## for the temporary code folder (folder will be deleted at end)
|
||||
folderPS=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)
|
||||
|
||||
#utility function to print to stderr
|
||||
echoerr() { echo "$@" 1>&2; }
|
||||
|
||||
display_usage() {
|
||||
echo -e "\e[90mUsage:
|
||||
|
||||
-t | --token [github_auth_token]
|
||||
Input an auth token to access the googlesamples GitHub org
|
||||
(if this is not present, you will be prompted for one later)
|
||||
|
||||
-s | --samples [sample1 sample2 sample3 ... sampleN]
|
||||
If you don't want to check the entire samples folder,
|
||||
you can specify which samples to use with this option.
|
||||
|
||||
--upload-only
|
||||
Only uploads new samples - samples with existing
|
||||
repos will be ignored
|
||||
|
||||
--update-only
|
||||
Only updates samples with existing repos - new
|
||||
samples will be ignored
|
||||
|
||||
--keep-temp-files
|
||||
Will not delete the temporary directory used to pull/push
|
||||
to Github. (normally deleted upon exit) Preserves logs.
|
||||
|
||||
This script can be run with no options - it will check the entire
|
||||
./prebuilts/gradle folder and prompt for an auth token when needed.\e[0m\n"
|
||||
}
|
||||
|
||||
##############################################################################
|
||||
## Make sure we delete the temporary folder (if it gets created) before exiting
|
||||
finish() {
|
||||
if $deleteTemp; then
|
||||
if [ -d "../github-temp$folderPS" ]; then
|
||||
cd ..
|
||||
rm -rf ./github-temp$folderPS
|
||||
elif [ -d "github-temp$folderPS" ]; then
|
||||
rm -rf ./github-temp$folderPS
|
||||
fi
|
||||
fi
|
||||
}
|
||||
# this ensures finish() will always be called no matter how the script ends
|
||||
trap finish EXIT
|
||||
|
||||
|
||||
##############################################################################
|
||||
## Process input parameters. (see above for usage)
|
||||
|
||||
## How this works:
|
||||
## $# is the number of parameters passed in
|
||||
## $1 is the first parameter, $2 the second, and so on. (space delimited)
|
||||
## shift basically left shifts the params array - $1 goes away, $2 becomes $1, etc
|
||||
## Thus, this while loop iterates through all command line parameters
|
||||
while [[ $# > 0 ]]; do
|
||||
case "$1" in
|
||||
|
||||
-t|--token)
|
||||
if [[ $2 != -* ]] && [[ $2 ]]; then
|
||||
token="$2"; shift
|
||||
else
|
||||
echoerr -e "Option $1 requires an argument. Cancelling script.\nUse --help to display usage."
|
||||
exit 1
|
||||
fi;;
|
||||
|
||||
--update-only) upload=false;;
|
||||
|
||||
--upload-only) update=false;;
|
||||
|
||||
--keep-temp-files) deleteTemp=false;;
|
||||
|
||||
-s|--samples)
|
||||
useAllSamples=false
|
||||
while [[ $2 != -* ]] && [[ $2 ]]; do
|
||||
#if true; then ##for testing
|
||||
if [ -d "./prebuilts/gradle/$2" ]; then
|
||||
allSamples+=("$2")
|
||||
shift
|
||||
else
|
||||
echoerr -e "Sample \"$2\" does not exist in ./prebuilts/gradle. Cancelling script.\n"
|
||||
exit 1
|
||||
fi
|
||||
done;;
|
||||
|
||||
-h|--help)
|
||||
display_usage
|
||||
exit 1;;
|
||||
|
||||
*)
|
||||
echoerr -e "Unknown Option: $1\nUse --help to display usage."
|
||||
exit 1;;
|
||||
|
||||
esac
|
||||
shift
|
||||
done #ends options while loop
|
||||
|
||||
if ! $upload && ! $update; then
|
||||
echoerr -e "Do not use both --update-only and --upload-only, no samples will be processed.
|
||||
If you want to do both updates and uploads, no flags are needed.
|
||||
Use --help to display usage."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
##############################################################################
|
||||
## Get all folders in prebuilts and stick 'em in an array
|
||||
|
||||
if $useAllSamples; then
|
||||
allSamples=($(ls ./prebuilts/gradle))
|
||||
fi
|
||||
|
||||
# [@] returns all items in an array, ${#...} counts them
|
||||
numSamples=${#allSamples[@]}
|
||||
echo "Running script for $numSamples samples"
|
||||
|
||||
##############################################################################
|
||||
## Iterate through all the samples and see if there's
|
||||
## a repo for them on GitHub already - save results so we only do it once
|
||||
|
||||
toUpdate=()
|
||||
toUpload=()
|
||||
problemSamples=()
|
||||
curSample=0
|
||||
|
||||
echo -ne "Checking for existence of repos... ($curSample/$numSamples)\r"
|
||||
for i in ${allSamples[@]};
|
||||
do
|
||||
#echo "$i"
|
||||
URL=https://github.com/googlesamples/android-$i
|
||||
result=$(curl -o /dev/null --silent --head --write-out '%{http_code}' "$URL")
|
||||
#echo "$result $URL"
|
||||
if [ "$result" -eq "404" ]; then
|
||||
toUpload+=("$i")
|
||||
elif [ "$result" -eq "200" ]; then
|
||||
toUpdate+=("$i")
|
||||
else
|
||||
problemSamples+=("$i")
|
||||
fi
|
||||
curSample=$(($curSample+1))
|
||||
echo -ne "Checking for existence of repos... ($curSample/$numSamples)\r"
|
||||
done #close for loop for existence check
|
||||
echo ""
|
||||
|
||||
|
||||
##############################################################################
|
||||
## For every sample that has a repo already, clone it and diff it against
|
||||
## the sample code in our git to see if it needs updating.
|
||||
|
||||
if $update; then
|
||||
|
||||
needsUpdate=()
|
||||
curSample=0
|
||||
numUpdates=${#toUpdate[@]}
|
||||
|
||||
##make temporary dir to pull code into - will be deleted upon exit.
|
||||
mkdir github-temp$folderPS
|
||||
cd github-temp$folderPS
|
||||
|
||||
echo -ne "Checking for out-of-date repos... ($curSample/$numUpdates)\r"
|
||||
for i in ${toUpdate[@]};
|
||||
do
|
||||
URL=https://github.com/googlesamples/android-$i
|
||||
git clone $URL.git &> /dev/null
|
||||
if [ -d "android-$i" ]; then
|
||||
diffResult=$(diff -r --exclude '*.git' ../prebuilts/gradle/$i/ ./android-$i/)
|
||||
#for testing (will show diff in every repo)
|
||||
#diffResult=$(diff -r ../prebuilts/gradle/$i/ ./android-$i/)`
|
||||
#echo $diffResult
|
||||
if [ -n "$diffResult" ]; then
|
||||
needsUpdate+=("$i")
|
||||
fi
|
||||
else
|
||||
echoerr "Something went wrong when cloning $i - result directory does not exist.
|
||||
Leaving temp files in place for further examination."
|
||||
deleteTemp=false;
|
||||
fi
|
||||
curSample=$(($curSample+1))
|
||||
echo -ne "Checking for out-of-date repos... ($curSample/$numUpdates)\r"
|
||||
done #end of for loop when checking which repos actually need updating
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
##############################################################################
|
||||
## Display the detected changes to be made and get user confirmation
|
||||
|
||||
if $upload; then
|
||||
if [ ${#toUpload[@]} -ne 0 ]; then
|
||||
echo -e "\n\e[1mNew samples that will be uploaded:\e[0m"
|
||||
for i in ${toUpload[@]}; do
|
||||
echo -e "\e[32m$i\e[0m"
|
||||
done
|
||||
else
|
||||
upload=false
|
||||
echo "Nothing new to upload."
|
||||
fi
|
||||
else
|
||||
echo "No uploads - check skipped on user request"
|
||||
fi
|
||||
|
||||
if $update; then
|
||||
if [ ${#needsUpdate[@]} -ne 0 ]; then
|
||||
echo -e "\n\e[1mSamples that will be updated:\e[0m"
|
||||
for i in ${needsUpdate[@]}; do
|
||||
echo -e "\e[34m$i\e[0m"
|
||||
done
|
||||
else
|
||||
update=false
|
||||
echo "Nothing to update."
|
||||
fi
|
||||
else
|
||||
echo "No updates - check skipped on user request"
|
||||
fi
|
||||
|
||||
if [ ${#problemSamples[@]} -ne 0 ]; then
|
||||
echoerr "
|
||||
These repos returned something other than a 404 or 200 result code:"
|
||||
for i in ${problemSamples[@]};
|
||||
do
|
||||
echoerr "$i"
|
||||
done
|
||||
fi
|
||||
|
||||
if ! $upload && ! $update; then
|
||||
echo -e "\e[1mLooks like everything's up-to-date.\e[0m\n"
|
||||
else
|
||||
|
||||
read -p "
|
||||
Do you want to continue? [y/n]: " -n 1 -r
|
||||
echo
|
||||
# if they type anything but an upper or lower case y, don't proceed.
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]
|
||||
then
|
||||
#echo "Commencing Github updates"
|
||||
|
||||
##############################################################################
|
||||
## If the user hasn't supplied a token via parameter, ask now
|
||||
|
||||
if ! [ -n "$token" ]
|
||||
then
|
||||
read -p "
|
||||
Input a valid googlesamples GitHub access token to continue: " -r
|
||||
token=$REPLY
|
||||
fi
|
||||
|
||||
##############################################################################
|
||||
## Test that token
|
||||
|
||||
tokenTest=$(curl -o /dev/null --silent \
|
||||
-H "Authorization: token $token" \
|
||||
--write-out '%{http_code}' "https://api.github.com/orgs/googlesamples/repos")
|
||||
|
||||
if [ "$tokenTest" -eq "200" ]; then
|
||||
|
||||
|
||||
##############################################################################
|
||||
## If there's something to update, do the updates
|
||||
if [ ${#needsUpdate[@]} -ne 0 ] && $update; then
|
||||
for i in ${needsUpdate[@]}; do
|
||||
echo -e "\nUpdating $i"
|
||||
if [ -d "android-$i" ]; then
|
||||
rsync -az --delete --exclude '*.git' ../prebuilts/gradle/$i/ ./android-$i/
|
||||
|
||||
cd ./android-$i/
|
||||
|
||||
git config user.name "google-automerger"
|
||||
git config user.email automerger@google.com
|
||||
|
||||
git add .
|
||||
git status
|
||||
git commit -m "Auto-update"
|
||||
|
||||
git remote set-url origin "https://$token@github.com/googlesamples/android-$i.git"
|
||||
git push origin master
|
||||
|
||||
#overwrite remote url to not contain auth token
|
||||
git remote set-url origin "http://github.com/googlesamples/android-$i.git"
|
||||
|
||||
cd ..
|
||||
else
|
||||
echoerr "Something went wrong when cloning $i - result directory does not exist.
|
||||
Leaving temp files in place for further examination."
|
||||
deleteTemp=false;
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
#moves out of the temp folder, if we're in it.
|
||||
if [ -d "../github-temp$folderPS" ]; then
|
||||
cd ..
|
||||
fi
|
||||
|
||||
##############################################################################
|
||||
## If there's something new to upload, do the uploads
|
||||
if [ ${#toUpload[@]} -ne 0 ] && $upload; then
|
||||
for i in ${toUpload[@]}; do
|
||||
echo -e "\nUploading $i"
|
||||
|
||||
repoName="googlesamples/android-$i"
|
||||
|
||||
CREATE="curl -H 'Authorization: token '$TOKEN \
|
||||
-d '{\"name\":\"android-'$i'\", \"team_id\":889859}' \
|
||||
https://api.github.com/orgs/googlesamples/repos"
|
||||
eval $CREATE
|
||||
|
||||
#add secondary team permissions (robots)
|
||||
ADDTEAM="curl -X PUT \
|
||||
-H 'Authorization: token '$TOKEN \
|
||||
-H 'Content-Length: 0' \
|
||||
https://api.github.com/teams/889856/repos/$repoName"
|
||||
|
||||
eval $ADDTEAM
|
||||
|
||||
URL="https://$token@github.com/$repoName"
|
||||
|
||||
cd $i
|
||||
git init
|
||||
#overrides .gitconfig just for this project - does not alter your global settings.
|
||||
git config user.name "google-automerger"
|
||||
git config user.email automerger@google.com
|
||||
git add .
|
||||
git commit -m "Initial Commit"
|
||||
git remote add origin $URL
|
||||
git push origin master
|
||||
cd ..
|
||||
|
||||
|
||||
done
|
||||
fi
|
||||
|
||||
else
|
||||
echoerr "That token doesn't work. A test returned the code: $tokenTest"
|
||||
fi
|
||||
|
||||
else
|
||||
echo "User cancelled Github update."
|
||||
fi
|
||||
|
||||
fi #end of "is there something to do?" if statement
|
BIN
android/developers/build/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
android/developers/build/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
#Tue May 17 22:12:39 PDT 2016
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
|
164
android/developers/build/gradlew
vendored
Executable file
|
@ -0,0 +1,164 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
90
android/developers/build/gradlew.bat
vendored
Normal file
|
@ -0,0 +1,90 @@
|
|||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
BIN
android/developers/build/lib/assetstudio.jar
Normal file
BIN
android/developers/build/lib/buildSrc.jar
Normal file
|
@ -0,0 +1,56 @@
|
|||
# How to become a contributor and submit your own code
|
||||
|
||||
## Contributor License Agreements
|
||||
|
||||
We'd love to accept your sample apps and patches! Before we can take them, we
|
||||
have to jump a couple of legal hurdles.
|
||||
|
||||
Please fill out either the individual or corporate Contributor License Agreement
|
||||
(CLA).
|
||||
|
||||
* If you are an individual writing original source code and you're sure you
|
||||
own the intellectual property, then you'll need to sign an [individual CLA]
|
||||
(http://code.google.com/legal/individual-cla-v1.0.html).
|
||||
* If you work for a company that wants to allow you to contribute your work,
|
||||
then you'll need to sign a [corporate CLA]
|
||||
(http://code.google.com/legal/corporate-cla-v1.0.html).
|
||||
|
||||
Follow either of the two links above to access the appropriate CLA and
|
||||
instructions for how to sign and return it. Once we receive it, we'll be able to
|
||||
accept your pull requests.
|
||||
|
||||
## Contributing a Patch
|
||||
|
||||
1. Sign a Contributor License Agreement, if you have not yet done so (see
|
||||
details above).
|
||||
1. Create your change to the repo in question.
|
||||
* Fork the desired repo, develop and test your code changes.
|
||||
* Ensure that your code is clear and comprehensible.
|
||||
* Ensure that your code has an appropriate set of unit tests which all pass.
|
||||
1. Submit a pull request.
|
||||
1. The repo owner will review your request. If it is approved, the change will
|
||||
be merged. If it needs additional work, the repo owner will respond with
|
||||
useful comments.
|
||||
|
||||
## Contributing a New Sample App
|
||||
|
||||
1. Sign a Contributor License Agreement, if you have not yet done so (see
|
||||
details above).
|
||||
1. Create your own repo for your app following this naming convention:
|
||||
* mirror-{app-name}-{language or plaform}
|
||||
* apps: quickstart, photohunt-server, photohunt-client
|
||||
* example: mirror-quickstart-android
|
||||
* For multi-language apps, concatenate the primary languages like this:
|
||||
mirror-photohunt-server-java-python.
|
||||
|
||||
1. Create your sample app in this repo.
|
||||
* Be sure to clone the README.md, CONTRIBUTING.md and LICENSE files from the
|
||||
googlesamples repo.
|
||||
* Ensure that your code is clear and comprehensible.
|
||||
* Ensure that your code has an appropriate set of unit tests which all pass.
|
||||
* Instructional value is the top priority when evaluating new app proposals for
|
||||
this collection of repos.
|
||||
1. Submit a request to fork your repo in googlesamples organization.
|
||||
1. The repo owner will review your request. If it is approved, the sample will
|
||||
be merged. If it needs additional work, the repo owner will respond with
|
||||
useful comments.
|
191
android/developers/build/prebuilts/androidtv/leanback/LICENSE
Normal file
|
@ -0,0 +1,191 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, "control" means (i) the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same "printed page" as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
|
@ -0,0 +1,25 @@
|
|||
# AndroidTV Leanback Support Library sample for videos
|
||||
|
||||
The Leanback API Demo/Video By Googles app is designed to run on an Android TV device and demonstrates how to use the Leanback Support library
|
||||
in order to comply with the UX guidelines of Android TV.
|
||||
|
||||
## Dependencies
|
||||
* Android SDK v7 appcompat library
|
||||
* Android SDK v17 leanback support library
|
||||
* Android SDK v7 recyclerview library
|
||||
|
||||
## Setup Instructions
|
||||
* Compile and deploy to your Android TV device.
|
||||
|
||||
## References and How to report bugs
|
||||
* [Android TV Developer Documentation](http://developer.android.com/tv)
|
||||
|
||||
## How to make contributions?
|
||||
Please read and follow the steps in the CONTRIBUTING.md
|
||||
|
||||
## License
|
||||
See LICENSE
|
||||
|
||||
## Google+
|
||||
Android TV Community Page on Google+ [https://g.co/androidtvdev](https://g.co/androidtvdev)
|
||||
## Change List
|
|
@ -0,0 +1,28 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 22
|
||||
buildToolsVersion '22.0.0'
|
||||
defaultConfig {
|
||||
applicationId "com.example.android.tvleanback"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 22
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile 'com.android.support:recyclerview-v7:22.0.0'
|
||||
compile 'com.android.support:leanback-v17:22.0.0'
|
||||
compile 'com.android.support:appcompat-v7:22.0.0'
|
||||
compile 'com.github.bumptech.glide:glide:3.4.+'
|
||||
compile 'com.android.support:support-v4:22.0.0'
|
||||
}
|
17
android/developers/build/prebuilts/androidtv/leanback/app/proguard-rules.pro
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /usr/local/google/home/cartland/android-sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
|
@ -0,0 +1,114 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2014 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.android.tvleanback"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.1" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="19"
|
||||
android:targetSdkVersion="21" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.microphone"
|
||||
android:required="false" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<uses-feature android:name="android.software.leanback"
|
||||
android:required="true" />
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:icon="@drawable/videos_by_google_banner"
|
||||
android:label="@string/app_name"
|
||||
android:logo="@drawable/videos_by_google_banner"
|
||||
android:theme="@style/Theme.Example.Leanback" >
|
||||
<activity
|
||||
android:name=".ui.MainActivity"
|
||||
android:icon="@drawable/videos_by_google_banner"
|
||||
android:label="@string/app_name"
|
||||
android:logo="@drawable/videos_by_google_banner"
|
||||
android:screenOrientation="landscape" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ui.MovieDetailsActivity"
|
||||
android:exported="true">
|
||||
|
||||
<!-- Receives the search request. -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
<!-- No category needed, because the Intent will specify this class component-->
|
||||
</intent-filter>
|
||||
|
||||
<!-- Points to searchable meta data. -->
|
||||
<meta-data android:name="android.app.searchable"
|
||||
android:resource="@xml/searchable" />
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ui.PlaybackOverlayActivity"
|
||||
android:exported="true" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.VerticalGridActivity"
|
||||
android:exported="true"
|
||||
android:parentActivityName=".ui.MainActivity" />
|
||||
<activity
|
||||
android:name=".ui.SearchActivity"
|
||||
android:exported="true" />
|
||||
|
||||
<activity android:name=".ui.BrowseErrorActivity"
|
||||
android:exported="true" />
|
||||
|
||||
<!-- Provides search suggestions for keywords against video meta data. -->
|
||||
<provider android:name=".data.VideoContentProvider"
|
||||
android:authorities="com.example.android.tvleanback"
|
||||
android:exported="true" />
|
||||
|
||||
<provider
|
||||
android:name=".recommendation.RecommendationBuilder$RecommendationBackgroundContentProvider"
|
||||
android:authorities="com.example.android.tvleanback.recommendation"
|
||||
android:exported="true" />
|
||||
|
||||
<receiver
|
||||
android:name=".recommendation.BootupActivity"
|
||||
android:enabled="true"
|
||||
android:exported="false" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".recommendation.UpdateRecommendationsService"
|
||||
android:enabled="true" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.leanback;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
/*
|
||||
* This class extends BroadCastReceiver and publishes recommendations on bootup
|
||||
*/
|
||||
public class BootupActivity extends BroadcastReceiver {
|
||||
private static final String TAG = "BootupActivity";
|
||||
|
||||
private static final long INITIAL_DELAY = 5000;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d(TAG, "BootupActivity initiated");
|
||||
if (intent.getAction().endsWith(Intent.ACTION_BOOT_COMPLETED)) {
|
||||
scheduleRecommendationUpdate(context);
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleRecommendationUpdate(Context context) {
|
||||
Log.d(TAG, "Scheduling recommendations update");
|
||||
|
||||
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
Intent recommendationIntent = new Intent(context, UpdateRecommendationsService.class);
|
||||
PendingIntent alarmIntent = PendingIntent.getService(context, 0, recommendationIntent, 0);
|
||||
|
||||
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
||||
INITIAL_DELAY,
|
||||
AlarmManager.INTERVAL_HALF_HOUR,
|
||||
alarmIntent);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.leanback;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v17.leanback.widget.ImageCardView;
|
||||
import android.support.v17.leanback.widget.Presenter;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.squareup.picasso.Target;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/*
|
||||
* A CardPresenter is used to generate Views and bind Objects to them on demand.
|
||||
* It contains an Image CardView
|
||||
*/
|
||||
public class CardPresenter extends Presenter {
|
||||
private static final String TAG = "CardPresenter";
|
||||
|
||||
private static Context mContext;
|
||||
private static int CARD_WIDTH = 313;
|
||||
private static int CARD_HEIGHT = 176;
|
||||
|
||||
static class ViewHolder extends Presenter.ViewHolder {
|
||||
private Movie mMovie;
|
||||
private ImageCardView mCardView;
|
||||
private Drawable mDefaultCardImage;
|
||||
private PicassoImageCardViewTarget mImageCardViewTarget;
|
||||
|
||||
public ViewHolder(View view) {
|
||||
super(view);
|
||||
mCardView = (ImageCardView) view;
|
||||
mImageCardViewTarget = new PicassoImageCardViewTarget(mCardView);
|
||||
mDefaultCardImage = mContext.getResources().getDrawable(R.drawable.movie);
|
||||
}
|
||||
|
||||
public void setMovie(Movie m) {
|
||||
mMovie = m;
|
||||
}
|
||||
|
||||
public Movie getMovie() {
|
||||
return mMovie;
|
||||
}
|
||||
|
||||
public ImageCardView getCardView() {
|
||||
return mCardView;
|
||||
}
|
||||
|
||||
protected void updateCardViewImage(URI uri) {
|
||||
Picasso.with(mContext)
|
||||
.load(uri.toString())
|
||||
.resize(Utils.dpToPx(CARD_WIDTH, mContext), Utils.dpToPx(CARD_HEIGHT, mContext))
|
||||
.error(mDefaultCardImage)
|
||||
.into(mImageCardViewTarget);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent) {
|
||||
Log.d(TAG, "onCreateViewHolder");
|
||||
mContext = parent.getContext();
|
||||
|
||||
ImageCardView cardView = new ImageCardView(mContext);
|
||||
cardView.setFocusable(true);
|
||||
cardView.setFocusableInTouchMode(true);
|
||||
cardView.setBackgroundColor(mContext.getResources().getColor(R.color.fastlane_background));
|
||||
return new ViewHolder(cardView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
|
||||
Movie movie = (Movie) item;
|
||||
((ViewHolder) viewHolder).setMovie(movie);
|
||||
|
||||
Log.d(TAG, "onBindViewHolder");
|
||||
if (movie.getCardImageUrl() != null) {
|
||||
((ViewHolder) viewHolder).mCardView.setTitleText(movie.getTitle());
|
||||
((ViewHolder) viewHolder).mCardView.setContentText(movie.getStudio());
|
||||
((ViewHolder) viewHolder).mCardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT);
|
||||
((ViewHolder) viewHolder).updateCardViewImage(movie.getCardImageURI());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
|
||||
Log.d(TAG, "onUnbindViewHolder");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewAttachedToWindow(Presenter.ViewHolder viewHolder) {
|
||||
// TO DO
|
||||
}
|
||||
|
||||
public static class PicassoImageCardViewTarget implements Target {
|
||||
private ImageCardView mImageCardView;
|
||||
|
||||
public PicassoImageCardViewTarget(ImageCardView imageCardView) {
|
||||
mImageCardView = imageCardView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom loadedFrom) {
|
||||
Drawable bitmapDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
|
||||
mImageCardView.setMainImage(bitmapDrawable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBitmapFailed(Drawable drawable) {
|
||||
mImageCardView.setMainImage(drawable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareLoad(Drawable drawable) {
|
||||
// Do nothing, default_background manager has its own transitions
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.leanback;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
/*
|
||||
* A wrapper class for details activity
|
||||
*/
|
||||
public class DetailsActivity extends Activity
|
||||
{
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.details);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.leanback;
|
||||
|
||||
import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
|
||||
|
||||
public class DetailsDescriptionPresenter extends AbstractDetailsDescriptionPresenter {
|
||||
|
||||
@Override
|
||||
protected void onBindDescription(ViewHolder viewHolder, Object item) {
|
||||
Movie movie = (Movie) item;
|
||||
|
||||
if (movie != null) {
|
||||
viewHolder.getTitle().setText(movie.getTitle());
|
||||
viewHolder.getSubtitle().setText(movie.getStudio());
|
||||
viewHolder.getBody().setText(movie.getDescription());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.leanback;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v17.leanback.app.BackgroundManager;
|
||||
import android.support.v17.leanback.app.DetailsFragment;
|
||||
import android.support.v17.leanback.widget.Action;
|
||||
import android.support.v17.leanback.widget.ArrayObjectAdapter;
|
||||
import android.support.v17.leanback.widget.ClassPresenterSelector;
|
||||
import android.support.v17.leanback.widget.DetailsOverviewRow;
|
||||
import android.support.v17.leanback.widget.DetailsOverviewRowPresenter;
|
||||
import android.support.v17.leanback.widget.HeaderItem;
|
||||
import android.support.v17.leanback.widget.ListRow;
|
||||
import android.support.v17.leanback.widget.ListRowPresenter;
|
||||
import android.support.v17.leanback.widget.OnActionClickedListener;
|
||||
import android.support.v17.leanback.widget.OnItemClickedListener;
|
||||
import android.support.v17.leanback.widget.Row;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.squareup.picasso.Target;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* LeanbackDetailsFragment extends DetailsFragment, a Wrapper fragment for leanback details screens.
|
||||
* It shows a detailed view of video and its meta plus related videos.
|
||||
*/
|
||||
public class LeanbackDetailsFragment extends DetailsFragment {
|
||||
private static final String TAG = "DetailsFragment";
|
||||
|
||||
private static final int ACTION_WATCH_TRAILER = 1;
|
||||
private static final int ACTION_RENT = 2;
|
||||
private static final int ACTION_BUY = 3;
|
||||
|
||||
private static final int DETAIL_THUMB_WIDTH = 274;
|
||||
private static final int DETAIL_THUMB_HEIGHT = 274;
|
||||
|
||||
private Movie selectedMovie;
|
||||
|
||||
private Drawable mDefaultBackground;
|
||||
private Target mBackgroundTarget;
|
||||
private DisplayMetrics mMetrics;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
Log.i(TAG, "onCreate DetailsFragment");
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity());
|
||||
backgroundManager.attach(getActivity().getWindow());
|
||||
mBackgroundTarget = new PicassoBackgroundManagerTarget(backgroundManager);
|
||||
|
||||
mDefaultBackground = getResources().getDrawable(R.drawable.default_background);
|
||||
|
||||
mMetrics = new DisplayMetrics();
|
||||
getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
|
||||
|
||||
selectedMovie = (Movie) getActivity().getIntent().getSerializableExtra("Movie");
|
||||
Log.d(TAG, "DetailsActivity movie: " + selectedMovie.toString());
|
||||
new DetailRowBuilderTask().execute(selectedMovie);
|
||||
|
||||
setOnItemClickedListener(getDefaultItemClickedListener());
|
||||
updateBackground(selectedMovie.getBackgroundImageURI());
|
||||
}
|
||||
|
||||
private class DetailRowBuilderTask extends AsyncTask<Movie, Integer, DetailsOverviewRow> {
|
||||
@Override
|
||||
protected DetailsOverviewRow doInBackground(Movie... movies) {
|
||||
selectedMovie = movies[0];
|
||||
|
||||
Log.d(TAG, "doInBackground: " + selectedMovie.toString());
|
||||
DetailsOverviewRow row = new DetailsOverviewRow(selectedMovie);
|
||||
try {
|
||||
Bitmap poster = Picasso.with(getActivity())
|
||||
.load(selectedMovie.getCardImageUrl())
|
||||
.resize(Utils.dpToPx(DETAIL_THUMB_WIDTH, getActivity()
|
||||
.getApplicationContext()),
|
||||
Utils.dpToPx(DETAIL_THUMB_HEIGHT, getActivity()
|
||||
.getApplicationContext()))
|
||||
.centerCrop()
|
||||
.get();
|
||||
row.setImageBitmap(getActivity(), poster);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
|
||||
row.addAction(new Action(ACTION_WATCH_TRAILER, getResources().getString(
|
||||
R.string.watch_trailer_1), getResources().getString(R.string.watch_trailer_2)));
|
||||
row.addAction(new Action(ACTION_RENT, getResources().getString(R.string.rent_1),
|
||||
getResources().getString(R.string.rent_2)));
|
||||
row.addAction(new Action(ACTION_BUY, getResources().getString(R.string.buy_1),
|
||||
getResources().getString(R.string.buy_2)));
|
||||
return row;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(DetailsOverviewRow detailRow) {
|
||||
ClassPresenterSelector ps = new ClassPresenterSelector();
|
||||
DetailsOverviewRowPresenter dorPresenter =
|
||||
new DetailsOverviewRowPresenter(new DetailsDescriptionPresenter());
|
||||
// set detail background and style
|
||||
dorPresenter.setBackgroundColor(getResources().getColor(R.color.detail_background));
|
||||
dorPresenter.setStyleLarge(true);
|
||||
dorPresenter.setOnActionClickedListener(new OnActionClickedListener() {
|
||||
@Override
|
||||
public void onActionClicked(Action action) {
|
||||
if (action.getId() == ACTION_WATCH_TRAILER) {
|
||||
Intent intent = new Intent(getActivity(), PlayerActivity.class);
|
||||
intent.putExtra(getResources().getString(R.string.movie), selectedMovie);
|
||||
intent.putExtra(getResources().getString(R.string.should_start), true);
|
||||
startActivity(intent);
|
||||
}
|
||||
else {
|
||||
Toast.makeText(getActivity(), action.toString(), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ps.addClassPresenter(DetailsOverviewRow.class, dorPresenter);
|
||||
ps.addClassPresenter(ListRow.class,
|
||||
new ListRowPresenter());
|
||||
|
||||
ArrayObjectAdapter adapter = new ArrayObjectAdapter(ps);
|
||||
adapter.add(detailRow);
|
||||
|
||||
String subcategories[] = {
|
||||
getString(R.string.related_movies)
|
||||
};
|
||||
HashMap<String, List<Movie>> movies = VideoProvider.getMovieList();
|
||||
|
||||
ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
|
||||
for (Map.Entry<String, List<Movie>> entry : movies.entrySet())
|
||||
{
|
||||
if (selectedMovie.getCategory().indexOf(entry.getKey()) >= 0) {
|
||||
List<Movie> list = entry.getValue();
|
||||
for (int j = 0; j < list.size(); j++) {
|
||||
listRowAdapter.add(list.get(j));
|
||||
}
|
||||
}
|
||||
}
|
||||
HeaderItem header = new HeaderItem(0, subcategories[0], null);
|
||||
adapter.add(new ListRow(header, listRowAdapter));
|
||||
|
||||
setAdapter(adapter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected OnItemClickedListener getDefaultItemClickedListener() {
|
||||
return new OnItemClickedListener() {
|
||||
@Override
|
||||
public void onItemClicked(Object item, Row row) {
|
||||
if (item instanceof Movie) {
|
||||
Movie movie = (Movie) item;
|
||||
Intent intent = new Intent(getActivity(), DetailsActivity.class);
|
||||
intent.putExtra(getResources().getString(R.string.movie), movie);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected void updateBackground(URI uri) {
|
||||
Picasso.with(getActivity())
|
||||
.load(uri.toString())
|
||||
.resize(mMetrics.widthPixels, mMetrics.heightPixels)
|
||||
.error(mDefaultBackground)
|
||||
.into(mBackgroundTarget);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.leanback;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
/*
|
||||
* A wrapper class for main view of the app
|
||||
*/
|
||||
public class MainActivity extends Activity {
|
||||
/** Called when the activity is first created. */
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.main);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,291 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.leanback;
|
||||
|
||||
import android.app.LoaderManager;
|
||||
import android.content.Intent;
|
||||
import android.content.Loader;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v17.leanback.app.BackgroundManager;
|
||||
import android.support.v17.leanback.app.BrowseFragment;
|
||||
import android.support.v17.leanback.widget.ArrayObjectAdapter;
|
||||
import android.support.v17.leanback.widget.HeaderItem;
|
||||
import android.support.v17.leanback.widget.ListRow;
|
||||
import android.support.v17.leanback.widget.ListRowPresenter;
|
||||
import android.support.v17.leanback.widget.OnItemClickedListener;
|
||||
import android.support.v17.leanback.widget.OnItemSelectedListener;
|
||||
import android.support.v17.leanback.widget.Presenter;
|
||||
import android.support.v17.leanback.widget.Row;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.squareup.picasso.Target;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
|
||||
/*
|
||||
* Main class to show BrowseFragment with header and rows of videos
|
||||
*/
|
||||
public class MainFragment extends BrowseFragment implements
|
||||
LoaderManager.LoaderCallbacks<HashMap<String, List<Movie>>> {
|
||||
private static final String TAG = "MainFragment";
|
||||
|
||||
private static int BACKGROUND_UPDATE_DELAY = 300;
|
||||
private static int GRID_ITEM_WIDTH = 200;
|
||||
private static int GRID_ITEM_HEIGHT = 200;
|
||||
|
||||
private ArrayObjectAdapter mRowsAdapter;
|
||||
private Drawable mDefaultBackground;
|
||||
private Target mBackgroundTarget;
|
||||
private DisplayMetrics mMetrics;
|
||||
private Timer mBackgroundTimer;
|
||||
private final Handler mHandler = new Handler();
|
||||
private URI mBackgroundURI;
|
||||
private static String mVideosUrl;
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
Log.i(TAG, "onCreate");
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
loadVideoData();
|
||||
|
||||
prepareBackgroundManager();
|
||||
setupUIElements();
|
||||
setupEventListeners();
|
||||
}
|
||||
|
||||
private void prepareBackgroundManager() {
|
||||
BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity());
|
||||
backgroundManager.attach(getActivity().getWindow());
|
||||
mBackgroundTarget = new PicassoBackgroundManagerTarget(backgroundManager);
|
||||
mDefaultBackground = getResources().getDrawable(R.drawable.default_background);
|
||||
mMetrics = new DisplayMetrics();
|
||||
getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
|
||||
}
|
||||
|
||||
private void setupUIElements() {
|
||||
// setBadgeDrawable(getActivity().getResources().getDrawable(R.drawable.videos_by_google_banner));
|
||||
setTitle(getString(R.string.browse_title)); // Badge, when set, takes precedent over title
|
||||
setHeadersState(HEADERS_ENABLED);
|
||||
setHeadersTransitionOnBackEnabled(true);
|
||||
// set fastLane (or headers) background color
|
||||
setBrandColor(getResources().getColor(R.color.fastlane_background));
|
||||
// set search icon color
|
||||
setSearchAffordanceColor(getResources().getColor(R.color.search_opaque));
|
||||
}
|
||||
|
||||
private void loadVideoData() {
|
||||
VideoProvider.setContext(getActivity());
|
||||
mVideosUrl = getActivity().getResources().getString(R.string.catalog_url);
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
private void setupEventListeners() {
|
||||
setOnSearchClickedListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent intent = new Intent(getActivity(), SearchActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
setOnItemSelectedListener(getDefaultItemSelectedListener());
|
||||
setOnItemClickedListener(getDefaultItemClickedListener());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader(int,
|
||||
* android.os.Bundle)
|
||||
*/
|
||||
@Override
|
||||
public Loader<HashMap<String, List<Movie>>> onCreateLoader(int arg0, Bundle arg1) {
|
||||
Log.d(TAG, "VideoItemLoader created ");
|
||||
return new VideoItemLoader(getActivity(), mVideosUrl);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see android.support.v4.app.LoaderManager.LoaderCallbacks#onLoadFinished(android
|
||||
* .support.v4.content.Loader, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public void onLoadFinished(Loader<HashMap<String, List<Movie>>> arg0,
|
||||
HashMap<String, List<Movie>> data) {
|
||||
|
||||
mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
|
||||
CardPresenter cardPresenter = new CardPresenter();
|
||||
|
||||
int i = 0;
|
||||
|
||||
for (Map.Entry<String, List<Movie>> entry : data.entrySet())
|
||||
{
|
||||
ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
|
||||
List<Movie> list = entry.getValue();
|
||||
|
||||
for (int j = 0; j < list.size(); j++) {
|
||||
listRowAdapter.add(list.get(j));
|
||||
}
|
||||
HeaderItem header = new HeaderItem(i, entry.getKey(), null);
|
||||
i++;
|
||||
mRowsAdapter.add(new ListRow(header, listRowAdapter));
|
||||
}
|
||||
|
||||
HeaderItem gridHeader = new HeaderItem(i, getResources().getString(R.string.preferences),
|
||||
null);
|
||||
|
||||
GridItemPresenter gridPresenter = new GridItemPresenter();
|
||||
ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(gridPresenter);
|
||||
gridRowAdapter.add(getResources().getString(R.string.grid_view));
|
||||
gridRowAdapter.add(getResources().getString(R.string.send_feeback));
|
||||
gridRowAdapter.add(getResources().getString(R.string.personal_settings));
|
||||
mRowsAdapter.add(new ListRow(gridHeader, gridRowAdapter));
|
||||
|
||||
setAdapter(mRowsAdapter);
|
||||
|
||||
updateRecommendations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<HashMap<String, List<Movie>>> arg0) {
|
||||
mRowsAdapter.clear();
|
||||
}
|
||||
|
||||
protected OnItemSelectedListener getDefaultItemSelectedListener() {
|
||||
return new OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(Object item, Row row) {
|
||||
if (item instanceof Movie) {
|
||||
mBackgroundURI = ((Movie) item).getBackgroundImageURI();
|
||||
startBackgroundTimer();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected OnItemClickedListener getDefaultItemClickedListener() {
|
||||
return new OnItemClickedListener() {
|
||||
@Override
|
||||
public void onItemClicked(Object item, Row row) {
|
||||
if (item instanceof Movie) {
|
||||
Movie movie = (Movie) item;
|
||||
Log.d(TAG, "Item: " + item.toString());
|
||||
Intent intent = new Intent(getActivity(), DetailsActivity.class);
|
||||
intent.putExtra(getString(R.string.movie), movie);
|
||||
startActivity(intent);
|
||||
}
|
||||
else if (item instanceof String) {
|
||||
if (((String) item).indexOf(getResources().getString(R.string.grid_view)) >= 0) {
|
||||
Intent intent = new Intent(getActivity(), VerticalGridActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
else {
|
||||
Toast.makeText(getActivity(), ((String) item), Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected void setDefaultBackground(Drawable background) {
|
||||
mDefaultBackground = background;
|
||||
}
|
||||
|
||||
protected void setDefaultBackground(int resourceId) {
|
||||
mDefaultBackground = getResources().getDrawable(resourceId);
|
||||
}
|
||||
|
||||
protected void updateBackground(URI uri) {
|
||||
Picasso.with(getActivity())
|
||||
.load(uri.toString())
|
||||
.resize(mMetrics.widthPixels, mMetrics.heightPixels)
|
||||
.centerCrop()
|
||||
.error(mDefaultBackground)
|
||||
.into(mBackgroundTarget);
|
||||
}
|
||||
|
||||
protected void updateBackground(Drawable drawable) {
|
||||
BackgroundManager.getInstance(getActivity()).setDrawable(drawable);
|
||||
}
|
||||
|
||||
protected void clearBackground() {
|
||||
BackgroundManager.getInstance(getActivity()).setDrawable(mDefaultBackground);
|
||||
}
|
||||
|
||||
private void startBackgroundTimer() {
|
||||
if (null != mBackgroundTimer) {
|
||||
mBackgroundTimer.cancel();
|
||||
}
|
||||
mBackgroundTimer = new Timer();
|
||||
mBackgroundTimer.schedule(new UpdateBackgroundTask(), BACKGROUND_UPDATE_DELAY);
|
||||
}
|
||||
|
||||
private class UpdateBackgroundTask extends TimerTask {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mBackgroundURI != null) {
|
||||
updateBackground(mBackgroundURI);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class GridItemPresenter extends Presenter {
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent) {
|
||||
TextView view = new TextView(parent.getContext());
|
||||
view.setLayoutParams(new ViewGroup.LayoutParams(GRID_ITEM_WIDTH, GRID_ITEM_HEIGHT));
|
||||
view.setFocusable(true);
|
||||
view.setFocusableInTouchMode(true);
|
||||
view.setBackgroundColor(getResources().getColor(R.color.default_background));
|
||||
view.setTextColor(Color.WHITE);
|
||||
view.setGravity(Gravity.CENTER);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder viewHolder, Object item) {
|
||||
((TextView) viewHolder.view).setText((String) item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnbindViewHolder(ViewHolder viewHolder) {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateRecommendations() {
|
||||
Intent recommendationIntent = new Intent(getActivity(), UpdateRecommendationsService.class);
|
||||
getActivity().startService(recommendationIntent);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.leanback;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
/*
|
||||
* Movie class represents video entity with title, description, image thumbs and video url.
|
||||
*
|
||||
*/
|
||||
public class Movie implements Serializable {
|
||||
static final long serialVersionUID = 727566175075960653L;
|
||||
private static long count = 0;
|
||||
private long id;
|
||||
private String title;
|
||||
private String description;
|
||||
private String bgImageUrl;
|
||||
private String cardImageUrl;
|
||||
private String videoUrl;
|
||||
private String studio;
|
||||
private String category;
|
||||
|
||||
public Movie() {
|
||||
}
|
||||
|
||||
public static long getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public static void incCount() {
|
||||
count++;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getStudio() {
|
||||
return studio;
|
||||
}
|
||||
|
||||
public void setStudio(String studio) {
|
||||
this.studio = studio;
|
||||
}
|
||||
|
||||
public String getVideoUrl() {
|
||||
return videoUrl;
|
||||
}
|
||||
|
||||
public void setVideoUrl(String videoUrl) {
|
||||
this.videoUrl = videoUrl;
|
||||
}
|
||||
|
||||
public String getBackgroundImageUrl() {
|
||||
return bgImageUrl;
|
||||
}
|
||||
|
||||
public void setBackgroundImageUrl(String bgImageUrl) {
|
||||
this.bgImageUrl = bgImageUrl;
|
||||
}
|
||||
|
||||
public String getCardImageUrl() {
|
||||
return cardImageUrl;
|
||||
}
|
||||
|
||||
public void setCardImageUrl(String cardImageUrl) {
|
||||
this.cardImageUrl = cardImageUrl;
|
||||
}
|
||||
|
||||
public String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(String category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public URI getBackgroundImageURI() {
|
||||
try {
|
||||
Log.d("BACK MOVIE: ", bgImageUrl);
|
||||
return new URI(getBackgroundImageUrl());
|
||||
} catch (URISyntaxException e) {
|
||||
Log.d("URI exception: ", bgImageUrl);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public URI getCardImageURI() {
|
||||
try {
|
||||
return new URI(getCardImageUrl());
|
||||
} catch (URISyntaxException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Movie{" +
|
||||
"id=" + id +
|
||||
", title='" + title + '\'' +
|
||||
", videoUrl='" + videoUrl + '\'' +
|
||||
", backgroundImageUrl='" + bgImageUrl + '\'' +
|
||||
", backgroundImageURI='" + getBackgroundImageURI().toString() + '\'' +
|
||||
", cardImageUrl='" + cardImageUrl + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.leanback;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v17.leanback.app.BackgroundManager;
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.squareup.picasso.Target;
|
||||
|
||||
/**
|
||||
* Picasso target for updating default_background images
|
||||
*/
|
||||
public class PicassoBackgroundManagerTarget implements Target {
|
||||
BackgroundManager mBackgroundManager;
|
||||
|
||||
public PicassoBackgroundManagerTarget(BackgroundManager backgroundManager) {
|
||||
this.mBackgroundManager = backgroundManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom loadedFrom) {
|
||||
this.mBackgroundManager.setBitmap(bitmap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBitmapFailed(Drawable drawable) {
|
||||
this.mBackgroundManager.setDrawable(drawable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareLoad(Drawable drawable) {
|
||||
// Do nothing, default_background manager has its own transitions
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
PicassoBackgroundManagerTarget that = (PicassoBackgroundManagerTarget) o;
|
||||
|
||||
if (!mBackgroundManager.equals(that.mBackgroundManager))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mBackgroundManager.hashCode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,451 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* 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.leanback;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.media.MediaPlayer;
|
||||
import android.media.MediaPlayer.OnCompletionListener;
|
||||
import android.media.MediaPlayer.OnErrorListener;
|
||||
import android.media.MediaPlayer.OnPreparedListener;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout.LayoutParams;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.VideoView;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/*
|
||||
* PlayerActivity handles video playback
|
||||
*/
|
||||
public class PlayerActivity extends Activity {
|
||||
|
||||
private static final String TAG = "PlayerActivity";
|
||||
|
||||
private static final int HIDE_CONTROLLER_TIME = 5000;
|
||||
private static final int SEEKBAR_DELAY_TIME = 100;
|
||||
private static final int SEEKBAR_INTERVAL_TIME = 1000;
|
||||
private static final int MIN_SCRUB_TIME = 3000;
|
||||
private static final int SCRUB_SEGMENT_DIVISOR = 30;
|
||||
private static final double MEDIA_BAR_TOP_MARGIN = 0.8;
|
||||
private static final double MEDIA_BAR_RIGHT_MARGIN = 0.2;
|
||||
private static final double MEDIA_BAR_BOTTOM_MARGIN = 0.0;
|
||||
private static final double MEDIA_BAR_LEFT_MARGIN = 0.2;
|
||||
private static final double MEDIA_BAR_HEIGHT = 0.1;
|
||||
private static final double MEDIA_BAR_WIDTH = 0.9;
|
||||
|
||||
private VideoView mVideoView;
|
||||
private TextView mStartText;
|
||||
private TextView mEndText;
|
||||
private SeekBar mSeekbar;
|
||||
private ImageView mPlayPause;
|
||||
private ProgressBar mLoading;
|
||||
private View mControllers;
|
||||
private View mContainer;
|
||||
private Timer mSeekbarTimer;
|
||||
private Timer mControllersTimer;
|
||||
private PlaybackState mPlaybackState;
|
||||
private final Handler mHandler = new Handler();
|
||||
private Movie mSelectedMovie;
|
||||
private boolean mShouldStartPlayback;
|
||||
private boolean mControllersVisible;
|
||||
private int mDuration;
|
||||
private DisplayMetrics mMetrics;
|
||||
|
||||
/*
|
||||
* List of various states that we can be in
|
||||
*/
|
||||
public static enum PlaybackState {
|
||||
PLAYING, PAUSED, BUFFERING, IDLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.player_activity);
|
||||
|
||||
mMetrics = new DisplayMetrics();
|
||||
getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
|
||||
|
||||
loadViews();
|
||||
setupController();
|
||||
setupControlsCallbacks();
|
||||
startVideoPlayer();
|
||||
updateMetadata(true);
|
||||
}
|
||||
|
||||
private void startVideoPlayer() {
|
||||
Bundle b = getIntent().getExtras();
|
||||
mSelectedMovie = (Movie) getIntent().getSerializableExtra(
|
||||
getResources().getString(R.string.movie));
|
||||
if (null != b) {
|
||||
mShouldStartPlayback = b.getBoolean(getResources().getString(R.string.should_start));
|
||||
int startPosition = b.getInt(getResources().getString(R.string.start_position), 0);
|
||||
mVideoView.setVideoPath(mSelectedMovie.getVideoUrl());
|
||||
if (mShouldStartPlayback) {
|
||||
mPlaybackState = PlaybackState.PLAYING;
|
||||
updatePlayButton(mPlaybackState);
|
||||
if (startPosition > 0) {
|
||||
mVideoView.seekTo(startPosition);
|
||||
}
|
||||
mVideoView.start();
|
||||
mPlayPause.requestFocus();
|
||||
startControllersTimer();
|
||||
} else {
|
||||
updatePlaybackLocation();
|
||||
mPlaybackState = PlaybackState.PAUSED;
|
||||
updatePlayButton(mPlaybackState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePlaybackLocation() {
|
||||
if (mPlaybackState == PlaybackState.PLAYING ||
|
||||
mPlaybackState == PlaybackState.BUFFERING) {
|
||||
startControllersTimer();
|
||||
} else {
|
||||
stopControllersTimer();
|
||||
}
|
||||
}
|
||||
|
||||
private void play(int position) {
|
||||
startControllersTimer();
|
||||
mVideoView.seekTo(position);
|
||||
mVideoView.start();
|
||||
restartSeekBarTimer();
|
||||
}
|
||||
|
||||
private void stopSeekBarTimer() {
|
||||
if (null != mSeekbarTimer) {
|
||||
mSeekbarTimer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void restartSeekBarTimer() {
|
||||
stopSeekBarTimer();
|
||||
mSeekbarTimer = new Timer();
|
||||
mSeekbarTimer.scheduleAtFixedRate(new UpdateSeekbarTask(), SEEKBAR_DELAY_TIME,
|
||||
SEEKBAR_INTERVAL_TIME);
|
||||
}
|
||||
|
||||
private void stopControllersTimer() {
|
||||
if (null != mControllersTimer) {
|
||||
mControllersTimer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void startControllersTimer() {
|
||||
if (null != mControllersTimer) {
|
||||
mControllersTimer.cancel();
|
||||
}
|
||||
mControllersTimer = new Timer();
|
||||
mControllersTimer.schedule(new HideControllersTask(), HIDE_CONTROLLER_TIME);
|
||||
}
|
||||
|
||||
private void updateControllersVisibility(boolean show) {
|
||||
if (show) {
|
||||
mControllers.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mControllers.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
Log.d(TAG, "onPause() was called");
|
||||
if (null != mSeekbarTimer) {
|
||||
mSeekbarTimer.cancel();
|
||||
mSeekbarTimer = null;
|
||||
}
|
||||
if (null != mControllersTimer) {
|
||||
mControllersTimer.cancel();
|
||||
}
|
||||
mVideoView.pause();
|
||||
mPlaybackState = PlaybackState.PAUSED;
|
||||
updatePlayButton(PlaybackState.PAUSED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
Log.d(TAG, "onStop() was called");
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
Log.d(TAG, "onDestroy() is called");
|
||||
stopControllersTimer();
|
||||
stopSeekBarTimer();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
Log.d(TAG, "onStart() was called");
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
Log.d(TAG, "onResume() was called");
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
private class HideControllersTask extends TimerTask {
|
||||
@Override
|
||||
public void run() {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateControllersVisibility(false);
|
||||
mControllersVisible = false;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateSeekbarTask extends TimerTask {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mHandler.post(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
int currentPos = 0;
|
||||
currentPos = mVideoView.getCurrentPosition();
|
||||
updateSeekbar(currentPos, mDuration);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class BackToDetailTask extends TimerTask {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Intent intent = new Intent(PlayerActivity.this, DetailsActivity.class);
|
||||
intent.putExtra(getResources().getString(R.string.movie), mSelectedMovie);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void setupController() {
|
||||
|
||||
int w = (int) (mMetrics.widthPixels * MEDIA_BAR_WIDTH);
|
||||
int h = (int) (mMetrics.heightPixels * MEDIA_BAR_HEIGHT);
|
||||
int marginLeft = (int) (mMetrics.widthPixels * MEDIA_BAR_LEFT_MARGIN);
|
||||
int marginTop = (int) (mMetrics.heightPixels * MEDIA_BAR_TOP_MARGIN);
|
||||
int marginRight = (int) (mMetrics.widthPixels * MEDIA_BAR_RIGHT_MARGIN);
|
||||
int marginBottom = (int) (mMetrics.heightPixels * MEDIA_BAR_BOTTOM_MARGIN);
|
||||
LayoutParams lp = new LayoutParams(w, h);
|
||||
lp.setMargins(marginLeft, marginTop, marginRight, marginBottom);
|
||||
mControllers.setLayoutParams(lp);
|
||||
mStartText.setText(getResources().getString(R.string.init_text));
|
||||
mEndText.setText(getResources().getString(R.string.init_text));
|
||||
}
|
||||
|
||||
private void setupControlsCallbacks() {
|
||||
|
||||
mVideoView.setOnErrorListener(new OnErrorListener() {
|
||||
|
||||
@Override
|
||||
public boolean onError(MediaPlayer mp, int what, int extra) {
|
||||
String msg = "";
|
||||
if (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) {
|
||||
msg = getString(R.string.video_error_media_load_timeout);
|
||||
} else if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
|
||||
msg = getString(R.string.video_error_server_unaccessible);
|
||||
} else {
|
||||
msg = getString(R.string.video_error_unknown_error);
|
||||
}
|
||||
Utils.showErrorDialog(PlayerActivity.this, msg);
|
||||
mVideoView.stopPlayback();
|
||||
mPlaybackState = PlaybackState.IDLE;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mVideoView.setOnPreparedListener(new OnPreparedListener() {
|
||||
|
||||
@Override
|
||||
public void onPrepared(MediaPlayer mp) {
|
||||
Log.d(TAG, "onPrepared is reached");
|
||||
mDuration = mp.getDuration();
|
||||
mEndText.setText(formatTimeSignature(mDuration));
|
||||
mSeekbar.setMax(mDuration);
|
||||
restartSeekBarTimer();
|
||||
}
|
||||
});
|
||||
|
||||
mVideoView.setOnCompletionListener(new OnCompletionListener() {
|
||||
|
||||
@Override
|
||||
public void onCompletion(MediaPlayer mp) {
|
||||
stopSeekBarTimer();
|
||||
mPlaybackState = PlaybackState.IDLE;
|
||||
updatePlayButton(PlaybackState.IDLE);
|
||||
mControllersTimer = new Timer();
|
||||
mControllersTimer.schedule(new BackToDetailTask(), HIDE_CONTROLLER_TIME);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return
|
||||
* super.onKeyDown(keyCode, event); }
|
||||
*/
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
int currentPos = 0;
|
||||
int delta = (int) (mDuration / SCRUB_SEGMENT_DIVISOR);
|
||||
if (delta < MIN_SCRUB_TIME)
|
||||
delta = MIN_SCRUB_TIME;
|
||||
|
||||
Log.v("keycode", "duration " + mDuration + " delta:" + delta);
|
||||
if (!mControllersVisible) {
|
||||
updateControllersVisibility(true);
|
||||
}
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||
return true;
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
return true;
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
currentPos = mVideoView.getCurrentPosition();
|
||||
currentPos -= delta;
|
||||
if (currentPos > 0)
|
||||
play(currentPos);
|
||||
return true;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
currentPos = mVideoView.getCurrentPosition();
|
||||
currentPos += delta;
|
||||
if (currentPos < mDuration)
|
||||
play(currentPos);
|
||||
return true;
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
private void updateSeekbar(int position, int duration) {
|
||||
mSeekbar.setProgress(position);
|
||||
mSeekbar.setMax(duration);
|
||||
mStartText.setText(formatTimeSignature(mDuration));
|
||||
}
|
||||
|
||||
private void updatePlayButton(PlaybackState state) {
|
||||
switch (state) {
|
||||
case PLAYING:
|
||||
mLoading.setVisibility(View.INVISIBLE);
|
||||
mPlayPause.setVisibility(View.VISIBLE);
|
||||
mPlayPause.setImageDrawable(
|
||||
getResources().getDrawable(R.drawable.ic_pause_playcontrol_normal));
|
||||
break;
|
||||
case PAUSED:
|
||||
case IDLE:
|
||||
mLoading.setVisibility(View.INVISIBLE);
|
||||
mPlayPause.setVisibility(View.VISIBLE);
|
||||
mPlayPause.setImageDrawable(
|
||||
getResources().getDrawable(R.drawable.ic_play_playcontrol_normal));
|
||||
break;
|
||||
case BUFFERING:
|
||||
mPlayPause.setVisibility(View.INVISIBLE);
|
||||
mLoading.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMetadata(boolean visible) {
|
||||
mVideoView.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void loadViews() {
|
||||
mVideoView = (VideoView) findViewById(R.id.videoView);
|
||||
mStartText = (TextView) findViewById(R.id.startText);
|
||||
mEndText = (TextView) findViewById(R.id.endText);
|
||||
mSeekbar = (SeekBar) findViewById(R.id.seekBar);
|
||||
mPlayPause = (ImageView) findViewById(R.id.playpause);
|
||||
mLoading = (ProgressBar) findViewById(R.id.progressBar);
|
||||
mControllers = findViewById(R.id.controllers);
|
||||
mContainer = findViewById(R.id.container);
|
||||
|
||||
mVideoView.setOnClickListener(mPlayPauseHandler);
|
||||
}
|
||||
|
||||
View.OnClickListener mPlayPauseHandler = new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
Log.d(TAG, "clicked play pause button");
|
||||
|
||||
if (!mControllersVisible) {
|
||||
updateControllersVisibility(true);
|
||||
}
|
||||
|
||||
if (mPlaybackState == PlaybackState.PAUSED) {
|
||||
mPlaybackState = PlaybackState.PLAYING;
|
||||
updatePlayButton(mPlaybackState);
|
||||
mVideoView.start();
|
||||
startControllersTimer();
|
||||
} else {
|
||||
mVideoView.pause();
|
||||
mPlaybackState = PlaybackState.PAUSED;
|
||||
updatePlayButton(PlaybackState.PAUSED);
|
||||
stopControllersTimer();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private String formatTimeSignature(int timeSignature) {
|
||||
return String.format(Locale.US,
|
||||
"%02d:%02d",
|
||||
TimeUnit.MILLISECONDS.toMinutes(timeSignature),
|
||||
TimeUnit.MILLISECONDS.toSeconds(timeSignature)
|
||||
-
|
||||
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS
|
||||
.toMinutes(timeSignature)));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.leanback;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/*
|
||||
* This class builds recommendations as notifications with videos as inputs.
|
||||
*/
|
||||
public class RecommendationBuilder {
|
||||
private static final String TAG = "RecommendationBuilder";
|
||||
|
||||
private static int CARD_WIDTH = 313;
|
||||
private static int CARD_HEIGHT = 176;
|
||||
|
||||
public static final String EXTRA_BACKGROUND_IMAGE_URL = "background_image_url";
|
||||
private Context mContext;
|
||||
private NotificationManager mNotificationManager;
|
||||
|
||||
private int mId;
|
||||
private int mPriority;
|
||||
private int mSmallIcon;
|
||||
private String mTitle;
|
||||
private String mDescription;
|
||||
private String mImageUri;
|
||||
private String mBackgroundUri;
|
||||
private PendingIntent mIntent;
|
||||
|
||||
public RecommendationBuilder() {
|
||||
}
|
||||
|
||||
public RecommendationBuilder setContext(Context context) {
|
||||
mContext = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RecommendationBuilder setId(int id) {
|
||||
mId = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RecommendationBuilder setPriority(int priority) {
|
||||
mPriority = priority;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RecommendationBuilder setTitle(String title) {
|
||||
mTitle = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RecommendationBuilder setDescription(String description) {
|
||||
mDescription = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RecommendationBuilder setImage(String uri) {
|
||||
mImageUri = uri;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RecommendationBuilder setBackground(String uri) {
|
||||
mBackgroundUri = uri;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RecommendationBuilder setIntent(PendingIntent intent) {
|
||||
mIntent = intent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RecommendationBuilder setSmallIcon(int resourceId) {
|
||||
mSmallIcon = resourceId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Notification build() throws IOException {
|
||||
|
||||
Log.d(TAG, "Building notification - " + this.toString());
|
||||
|
||||
if (mNotificationManager == null) {
|
||||
mNotificationManager = (NotificationManager) mContext
|
||||
.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
}
|
||||
|
||||
Bundle extras = new Bundle();
|
||||
if (mBackgroundUri != null) {
|
||||
extras.putString(EXTRA_BACKGROUND_IMAGE_URL, mBackgroundUri);
|
||||
}
|
||||
|
||||
Bitmap image = Picasso.with(mContext)
|
||||
.load(mImageUri)
|
||||
.resize(Utils.dpToPx(CARD_WIDTH, mContext), Utils.dpToPx(CARD_HEIGHT, mContext))
|
||||
.get();
|
||||
|
||||
Notification notification = new NotificationCompat.BigPictureStyle(
|
||||
new NotificationCompat.Builder(mContext)
|
||||
.setContentTitle(mTitle)
|
||||
.setContentText(mDescription)
|
||||
.setPriority(mPriority)
|
||||
.setLocalOnly(true)
|
||||
.setOngoing(true)
|
||||
.setColor(mContext.getResources().getColor(R.color.fastlane_background))
|
||||
// .setCategory(Notification.CATEGORY_RECOMMENDATION)
|
||||
.setCategory("recommendation")
|
||||
.setLargeIcon(image)
|
||||
.setSmallIcon(mSmallIcon)
|
||||
.setContentIntent(mIntent)
|
||||
.setExtras(extras))
|
||||
.build();
|
||||
|
||||
mNotificationManager.notify(mId, notification);
|
||||
mNotificationManager = null;
|
||||
return notification;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RecommendationBuilder{" +
|
||||
", mId=" + mId +
|
||||
", mPriority=" + mPriority +
|
||||
", mSmallIcon=" + mSmallIcon +
|
||||
", mTitle='" + mTitle + '\'' +
|
||||
", mDescription='" + mDescription + '\'' +
|
||||
", mImageUri='" + mImageUri + '\'' +
|
||||
", mBackgroundUri='" + mBackgroundUri + '\'' +
|
||||
", mIntent=" + mIntent +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.leanback;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
/*
|
||||
* This class is a wrapper activity for SearchFragment
|
||||
*/
|
||||
public class SearchActivity extends Activity
|
||||
{
|
||||
/** Called when the activity is first created. */
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.search);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.leanback;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v17.leanback.widget.ArrayObjectAdapter;
|
||||
import android.support.v17.leanback.widget.HeaderItem;
|
||||
import android.support.v17.leanback.widget.ListRow;
|
||||
import android.support.v17.leanback.widget.ListRowPresenter;
|
||||
import android.support.v17.leanback.widget.ObjectAdapter;
|
||||
import android.support.v17.leanback.widget.OnItemClickedListener;
|
||||
import android.support.v17.leanback.widget.Row;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
/*
|
||||
* This class demonstrates how to do in-app search
|
||||
*/
|
||||
@SuppressLint("DefaultLocale")
|
||||
public class SearchFragment extends android.support.v17.leanback.app.SearchFragment
|
||||
implements android.support.v17.leanback.app.SearchFragment.SearchResultProvider {
|
||||
private static final String TAG = "SearchFragment";
|
||||
private static final int SEARCH_DELAY_MS = 300;
|
||||
|
||||
private ArrayObjectAdapter mRowsAdapter;
|
||||
private Handler mHandler = new Handler();
|
||||
private SearchRunnable mDelayedLoad;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
|
||||
setSearchResultProvider(this);
|
||||
setOnItemClickedListener(getDefaultItemClickedListener());
|
||||
mDelayedLoad = new SearchRunnable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectAdapter getResultsAdapter() {
|
||||
return mRowsAdapter;
|
||||
}
|
||||
|
||||
private void queryByWords(String words) {
|
||||
mRowsAdapter.clear();
|
||||
if (!TextUtils.isEmpty(words)) {
|
||||
mDelayedLoad.setSearchQuery(words);
|
||||
mHandler.removeCallbacks(mDelayedLoad);
|
||||
mHandler.postDelayed(mDelayedLoad, SEARCH_DELAY_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newQuery) {
|
||||
Log.i(TAG, String.format("Search Query Text Change %s", newQuery));
|
||||
queryByWords(newQuery);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
Log.i(TAG, String.format("Search Query Text Submit %s", query));
|
||||
queryByWords(query);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void loadRows(String query) {
|
||||
HashMap<String, List<Movie>> movies = VideoProvider.getMovieList();
|
||||
ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
|
||||
for (Map.Entry<String, List<Movie>> entry : movies.entrySet())
|
||||
{
|
||||
for (int i = 0; i < entry.getValue().size(); i++) {
|
||||
Movie movie = entry.getValue().get(i);
|
||||
if (movie.getTitle().toLowerCase(Locale.ENGLISH)
|
||||
.indexOf(query.toLowerCase(Locale.ENGLISH)) >= 0
|
||||
|| movie.getDescription().toLowerCase(Locale.ENGLISH)
|
||||
.indexOf(query.toLowerCase(Locale.ENGLISH)) >= 0) {
|
||||
listRowAdapter.add(movie);
|
||||
}
|
||||
}
|
||||
}
|
||||
HeaderItem header = new HeaderItem(0, getResources().getString(R.string.search_results),
|
||||
null);
|
||||
mRowsAdapter.add(new ListRow(header, listRowAdapter));
|
||||
}
|
||||
|
||||
protected OnItemClickedListener getDefaultItemClickedListener() {
|
||||
return new OnItemClickedListener() {
|
||||
@Override
|
||||
public void onItemClicked(Object item, Row row) {
|
||||
if (item instanceof Movie) {
|
||||
Movie movie = (Movie) item;
|
||||
Intent intent = new Intent(getActivity(), DetailsActivity.class);
|
||||
intent.putExtra(getResources().getString(R.string.movie), movie);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private class SearchRunnable implements Runnable {
|
||||
|
||||
private volatile String searchQuery;
|
||||
|
||||
public SearchRunnable() {
|
||||
}
|
||||
|
||||
public void run() {
|
||||
loadRows(searchQuery);
|
||||
}
|
||||
|
||||
public void setSearchQuery(String value) {
|
||||
this.searchQuery = value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.leanback;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.TaskStackBuilder;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* This class builds up to MAX_RECOMMMENDATIONS of recommendations and defines what happens
|
||||
* when they're clicked from Recommendations seciton on Home screen
|
||||
*/
|
||||
public class UpdateRecommendationsService extends IntentService {
|
||||
private static final String TAG = "UpdateRecommendationsService";
|
||||
private static final int MAX_RECOMMENDATIONS = 3;
|
||||
|
||||
public UpdateRecommendationsService() {
|
||||
super("RecommendationService");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
Log.d(TAG, "Updating recommendation cards");
|
||||
HashMap<String, List<Movie>> recommendations = VideoProvider.getMovieList();
|
||||
|
||||
int count = 0;
|
||||
|
||||
try {
|
||||
RecommendationBuilder builder = new RecommendationBuilder()
|
||||
.setContext(getApplicationContext())
|
||||
.setSmallIcon(R.drawable.videos_by_google_icon);
|
||||
|
||||
for (Map.Entry<String, List<Movie>> entry : recommendations.entrySet())
|
||||
{
|
||||
for (int i = 0; i < entry.getValue().size(); i++) {
|
||||
Movie movie = entry.getValue().get(i);
|
||||
Log.d(TAG, "Recommendation - " + movie.getTitle());
|
||||
|
||||
builder.setBackground(movie.getCardImageUrl())
|
||||
.setId(count + 1)
|
||||
.setPriority(MAX_RECOMMENDATIONS - count)
|
||||
.setTitle(movie.getTitle())
|
||||
.setDescription(getString(R.string.popular_header))
|
||||
.setImage(movie.getCardImageUrl())
|
||||
.setIntent(buildPendingIntent(movie))
|
||||
.build();
|
||||
|
||||
if (++count >= MAX_RECOMMENDATIONS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (++count >= MAX_RECOMMENDATIONS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to update recommendation", e);
|
||||
}
|
||||
}
|
||||
|
||||
private PendingIntent buildPendingIntent(Movie movie) {
|
||||
Intent detailsIntent = new Intent(this, DetailsActivity.class);
|
||||
detailsIntent.putExtra("Movie", movie);
|
||||
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
|
||||
stackBuilder.addParentStack(DetailsActivity.class);
|
||||
stackBuilder.addNextIntent(detailsIntent);
|
||||
// Ensure a unique PendingIntents, otherwise all recommendations end up with the same
|
||||
// PendingIntent
|
||||
detailsIntent.setAction(Long.toString(movie.getId()));
|
||||
|
||||
PendingIntent intent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
return intent;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* 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.leanback;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.Point;
|
||||
import android.view.Display;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* A collection of utility methods, all static.
|
||||
*/
|
||||
public class Utils {
|
||||
|
||||
/*
|
||||
* Making sure public utility methods remain static
|
||||
*/
|
||||
private Utils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the screen/display size
|
||||
*
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
public static Point getDisplaySize(Context context) {
|
||||
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
Display display = wm.getDefaultDisplay();
|
||||
Point size = new Point();
|
||||
display.getSize(size);
|
||||
int width = size.x;
|
||||
int height = size.y;
|
||||
return new Point(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an error dialog with a given text message.
|
||||
*
|
||||
* @param context
|
||||
* @param errorString
|
||||
*/
|
||||
|
||||
public static final void showErrorDialog(Context context, String errorString) {
|
||||
new AlertDialog.Builder(context).setTitle(R.string.error)
|
||||
.setMessage(errorString)
|
||||
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.cancel();
|
||||
}
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a (long) toast
|
||||
*
|
||||
* @param context
|
||||
* @param msg
|
||||
*/
|
||||
public static void showToast(Context context, String msg) {
|
||||
Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a (long) toast.
|
||||
*
|
||||
* @param context
|
||||
* @param resourceId
|
||||
*/
|
||||
public static void showToast(Context context, int resourceId) {
|
||||
Toast.makeText(context, context.getString(resourceId), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
public static int dpToPx(int dp, Context ctx) {
|
||||
float density = ctx.getResources().getDisplayMetrics().density;
|
||||
return Math.round((float) dp * density);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.leanback;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
/*
|
||||
* Wrapper class for VerticalGridFragment
|
||||
*/
|
||||
public class VerticalGridActivity extends Activity
|
||||
{
|
||||
/** Called when the activity is first created. */
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.vertical_grid);
|
||||
getWindow().setBackgroundDrawableResource(R.drawable.grid_bg);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.leanback;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v17.leanback.widget.ArrayObjectAdapter;
|
||||
import android.support.v17.leanback.widget.OnItemClickedListener;
|
||||
import android.support.v17.leanback.widget.OnItemSelectedListener;
|
||||
import android.support.v17.leanback.widget.Row;
|
||||
import android.support.v17.leanback.widget.VerticalGridPresenter;
|
||||
import android.util.Log;
|
||||
|
||||
/*
|
||||
* VerticalGridFragment shows a grid of videos
|
||||
*/
|
||||
public class VerticalGridFragment extends android.support.v17.leanback.app.VerticalGridFragment {
|
||||
private static final String TAG = "VerticalGridFragment";
|
||||
|
||||
private static final int NUM_COLUMNS = 5;
|
||||
|
||||
private ArrayObjectAdapter mAdapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
Log.i(TAG, "onCreate");
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setTitle(getString(R.string.vertical_grid_title));
|
||||
|
||||
setupFragment();
|
||||
}
|
||||
|
||||
private void setupFragment() {
|
||||
VerticalGridPresenter gridPresenter = new VerticalGridPresenter();
|
||||
gridPresenter.setNumberOfColumns(NUM_COLUMNS);
|
||||
setGridPresenter(gridPresenter);
|
||||
|
||||
mAdapter = new ArrayObjectAdapter(new CardPresenter());
|
||||
|
||||
long seed = System.nanoTime();
|
||||
|
||||
HashMap<String, List<Movie>> movies = VideoProvider.getMovieList();
|
||||
|
||||
for (Map.Entry<String, List<Movie>> entry : movies.entrySet())
|
||||
{
|
||||
List<Movie> list = entry.getValue();
|
||||
Collections.shuffle(list, new Random(seed));
|
||||
for (int j = 0; j < list.size(); j++) {
|
||||
mAdapter.add(list.get(j));
|
||||
}
|
||||
}
|
||||
|
||||
setAdapter(mAdapter);
|
||||
|
||||
setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(Object item, Row row) {
|
||||
}
|
||||
});
|
||||
|
||||
setOnItemClickedListener(new OnItemClickedListener() {
|
||||
@Override
|
||||
public void onItemClicked(Object item, Row row) {
|
||||
if (item instanceof Movie) {
|
||||
Movie movie = (Movie) item;
|
||||
Intent intent = new Intent(getActivity(), DetailsActivity.class);
|
||||
intent.putExtra(getString(R.string.movie), movie);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* 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.leanback;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.AsyncTaskLoader;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
/*
|
||||
* This class asynchronously loads videos from a backend
|
||||
*/
|
||||
public class VideoItemLoader extends AsyncTaskLoader<HashMap<String, List<Movie>>> {
|
||||
|
||||
private static final String TAG = "VideoItemLoader";
|
||||
private final String mUrl;
|
||||
private Context mContext;
|
||||
|
||||
public VideoItemLoader(Context context, String url) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mUrl = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<String, List<Movie>> loadInBackground() {
|
||||
try {
|
||||
return VideoProvider.buildMedia(mContext, mUrl);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to fetch media data", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
super.onStartLoading();
|
||||
forceLoad();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request to stop the Loader.
|
||||
*/
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
// Attempt to cancel the current load task if possible.
|
||||
cancelLoad();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* 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.leanback;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* This class loads videos from a backend and saves them into a HashMap
|
||||
*/
|
||||
public class VideoProvider {
|
||||
|
||||
private static final String TAG = "VideoProvider";
|
||||
private static String TAG_MEDIA = "videos";
|
||||
private static String TAG_GOOGLE_VIDEOS = "googlevideos";
|
||||
private static String TAG_CATEGORY = "category";
|
||||
private static String TAG_STUDIO = "studio";
|
||||
private static String TAG_SOURCES = "sources";
|
||||
private static String TAG_DESCRIPTION = "description";
|
||||
private static String TAG_CARD_THUMB = "card";
|
||||
private static String TAG_BACKGROUND = "background";
|
||||
private static String TAG_TITLE = "title";
|
||||
|
||||
private static HashMap<String, List<Movie>> mMovieList;
|
||||
private static Context mContext;
|
||||
private static String mPrefixUrl;
|
||||
|
||||
public static void setContext(Context context) {
|
||||
if (mContext == null)
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
protected JSONObject parseUrl(String urlString) {
|
||||
Log.d(TAG, "Parse URL: " + urlString);
|
||||
InputStream is = null;
|
||||
|
||||
mPrefixUrl = mContext.getResources().getString(R.string.prefix_url);
|
||||
|
||||
try {
|
||||
java.net.URL url = new java.net.URL(urlString);
|
||||
URLConnection urlConnection = url.openConnection();
|
||||
is = new BufferedInputStream(urlConnection.getInputStream());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(
|
||||
urlConnection.getInputStream(), "iso-8859-1"), 8);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line = null;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
String json = sb.toString();
|
||||
return new JSONObject(json);
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "Failed to parse the json for media list", e);
|
||||
return null;
|
||||
} finally {
|
||||
if (null != is) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "JSON feed closed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static HashMap<String, List<Movie>> getMovieList() {
|
||||
return mMovieList;
|
||||
}
|
||||
|
||||
public static HashMap<String, List<Movie>> buildMedia(Context ctx, String url)
|
||||
throws JSONException {
|
||||
if (null != mMovieList) {
|
||||
return mMovieList;
|
||||
}
|
||||
mMovieList = new HashMap<String, List<Movie>>();
|
||||
|
||||
JSONObject jsonObj = new VideoProvider().parseUrl(url);
|
||||
JSONArray categories = jsonObj.getJSONArray(TAG_GOOGLE_VIDEOS);
|
||||
if (null != categories) {
|
||||
Log.d(TAG, "category #: " + categories.length());
|
||||
String title = new String();
|
||||
String videoUrl = new String();
|
||||
String bgImageUrl = new String();
|
||||
String cardImageUrl = new String();
|
||||
String studio = new String();
|
||||
for (int i = 0; i < categories.length(); i++) {
|
||||
JSONObject category = categories.getJSONObject(i);
|
||||
String category_name = category.getString(TAG_CATEGORY);
|
||||
JSONArray videos = category.getJSONArray(TAG_MEDIA);
|
||||
Log.d(TAG,
|
||||
"category: " + i + " Name:" + category_name + " video length: "
|
||||
+ videos.length());
|
||||
List<Movie> categoryList = new ArrayList<Movie>();
|
||||
if (null != videos) {
|
||||
for (int j = 0; j < videos.length(); j++) {
|
||||
JSONObject video = videos.getJSONObject(j);
|
||||
String description = video.getString(TAG_DESCRIPTION);
|
||||
JSONArray videoUrls = video.getJSONArray(TAG_SOURCES);
|
||||
if (null == videoUrls || videoUrls.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
title = video.getString(TAG_TITLE);
|
||||
videoUrl = getVideoPrefix(category_name, videoUrls.getString(0));
|
||||
bgImageUrl = getThumbPrefix(category_name, title,
|
||||
video.getString(TAG_BACKGROUND));
|
||||
cardImageUrl = getThumbPrefix(category_name, title,
|
||||
video.getString(TAG_CARD_THUMB));
|
||||
studio = video.getString(TAG_STUDIO);
|
||||
categoryList.add(buildMovieInfo(category_name, title, description, studio,
|
||||
videoUrl, cardImageUrl,
|
||||
bgImageUrl));
|
||||
}
|
||||
mMovieList.put(category_name, categoryList);
|
||||
}
|
||||
}
|
||||
}
|
||||
return mMovieList;
|
||||
}
|
||||
|
||||
private static Movie buildMovieInfo(String category, String title,
|
||||
String description, String studio, String videoUrl, String cardImageUrl,
|
||||
String bgImageUrl) {
|
||||
Movie movie = new Movie();
|
||||
movie.setId(Movie.getCount());
|
||||
Movie.incCount();
|
||||
movie.setTitle(title);
|
||||
movie.setDescription(description);
|
||||
movie.setStudio(studio);
|
||||
movie.setCategory(category);
|
||||
movie.setCardImageUrl(cardImageUrl);
|
||||
movie.setBackgroundImageUrl(bgImageUrl);
|
||||
movie.setVideoUrl(videoUrl);
|
||||
|
||||
return movie;
|
||||
}
|
||||
|
||||
private static String getVideoPrefix(String category, String videoUrl) {
|
||||
String ret = "";
|
||||
ret = mPrefixUrl + category.replace(" ", "%20") + '/' +
|
||||
videoUrl.replace(" ", "%20");
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static String getThumbPrefix(String category, String title, String imageUrl) {
|
||||
String ret = "";
|
||||
|
||||
ret = mPrefixUrl + category.replace(" ", "%20") + '/' +
|
||||
title.replace(" ", "%20") + '/' +
|
||||
imageUrl.replace(" ", "%20");
|
||||
return ret;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (C) 2015 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.tvleanback;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.view.Display;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* A collection of utility methods, all static.
|
||||
*/
|
||||
public class Utils {
|
||||
|
||||
/*
|
||||
* Making sure public utility methods remain static
|
||||
*/
|
||||
private Utils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the screen/display size
|
||||
*/
|
||||
public static Point getDisplaySize(Context context) {
|
||||
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
Display display = wm.getDefaultDisplay();
|
||||
Point size = new Point();
|
||||
display.getSize(size);
|
||||
int width = size.x;
|
||||
int height = size.y;
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a (long) toast
|
||||
*/
|
||||
public static void showToast(Context context, String msg) {
|
||||
Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a (long) toast.
|
||||
*/
|
||||
public static void showToast(Context context, int resourceId) {
|
||||
Toast.makeText(context, context.getString(resourceId), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
public static int convertDpToPixel(Context ctx, int dp) {
|
||||
float density = ctx.getResources().getDisplayMetrics().density;
|
||||
return Math.round((float) dp * density);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
* Copyright (C) 2015 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.tvleanback.data;
|
||||
|
||||
import android.database.AbstractCursor;
|
||||
import android.database.Cursor;
|
||||
|
||||
/**
|
||||
* A sample paginated cursor which will pre-fetch and cache rows.
|
||||
*/
|
||||
public class PaginatedCursor extends AbstractCursor {
|
||||
/**
|
||||
* The number of items that should be loaded each time.
|
||||
*/
|
||||
private static final int PAGE_SIZE = 10;
|
||||
|
||||
/**
|
||||
* The threshold of number of items left that a new page should be loaded.
|
||||
*/
|
||||
private static final int PAGE_THRESHOLD = PAGE_SIZE / 2;
|
||||
|
||||
private final Cursor mCursor;
|
||||
private final int mRowCount;
|
||||
private final boolean[] mCachedRows;
|
||||
private final String[] mColumnNames;
|
||||
private final int mColumnCount;
|
||||
private final int[] mColumnTypes;
|
||||
|
||||
private final byte[][][] mByteArrayDataCache;
|
||||
private final float[][] mFloatDataCache;
|
||||
private final int[][] mIntDataCache;
|
||||
private final String[][] mStringDataCache;
|
||||
/**
|
||||
* Index mapping from column index into the data type specific cache index;
|
||||
*/
|
||||
private final int[] mByteArrayCacheIndexMap;
|
||||
private final int[] mFloatCacheIndexMap;
|
||||
private final int[] mIntCacheIndexMap;
|
||||
private final int[] mStringCacheIndexMap;
|
||||
private int mByteArrayCacheColumnSize;
|
||||
private int mFloatCacheColumnSize;
|
||||
private int mIntCacheColumnSize;
|
||||
private int mStringCacheColumnSize;
|
||||
private int mLastCachePosition;
|
||||
|
||||
public PaginatedCursor(Cursor cursor) {
|
||||
super();
|
||||
mCursor = cursor;
|
||||
mRowCount = mCursor.getCount();
|
||||
mCachedRows = new boolean[mRowCount];
|
||||
mColumnNames = mCursor.getColumnNames();
|
||||
mColumnCount = mCursor.getColumnCount();
|
||||
mColumnTypes = new int[mColumnCount];
|
||||
|
||||
mByteArrayCacheColumnSize = 0;
|
||||
mFloatCacheColumnSize = 0;
|
||||
mIntCacheColumnSize = 0;
|
||||
mStringCacheColumnSize = 0;
|
||||
|
||||
mByteArrayCacheIndexMap = new int[mColumnCount];
|
||||
mFloatCacheIndexMap = new int[mColumnCount];
|
||||
mIntCacheIndexMap = new int[mColumnCount];
|
||||
mStringCacheIndexMap = new int[mColumnCount];
|
||||
|
||||
mCursor.moveToFirst();
|
||||
for (int i = 0; i < mColumnCount; i++) {
|
||||
int type = mCursor.getType(i);
|
||||
mColumnTypes[i] = type;
|
||||
switch (type) {
|
||||
case Cursor.FIELD_TYPE_BLOB:
|
||||
mByteArrayCacheIndexMap[i] = mByteArrayCacheColumnSize++;
|
||||
break;
|
||||
case Cursor.FIELD_TYPE_FLOAT:
|
||||
mFloatCacheIndexMap[i] = mFloatCacheColumnSize++;
|
||||
break;
|
||||
case Cursor.FIELD_TYPE_INTEGER:
|
||||
mIntCacheIndexMap[i] = mIntCacheColumnSize++;
|
||||
break;
|
||||
case Cursor.FIELD_TYPE_STRING:
|
||||
mStringCacheIndexMap[i] = mStringCacheColumnSize++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mByteArrayDataCache = mByteArrayCacheColumnSize > 0 ? new byte[mRowCount][][] : null;
|
||||
mFloatDataCache = mFloatCacheColumnSize > 0 ? new float[mRowCount][] : null;
|
||||
mIntDataCache = mIntCacheColumnSize > 0 ? new int[mRowCount][] : null;
|
||||
mStringDataCache = mStringCacheColumnSize > 0 ? new String[mRowCount][] : null;
|
||||
|
||||
for (int i = 0; i < mRowCount; i++) {
|
||||
mCachedRows[i] = false;
|
||||
if (mByteArrayDataCache != null) {
|
||||
mByteArrayDataCache[i] = new byte[mByteArrayCacheColumnSize][];
|
||||
}
|
||||
if (mFloatDataCache != null) {
|
||||
mFloatDataCache[i] = new float[mFloatCacheColumnSize];
|
||||
}
|
||||
if (mIntDataCache != null) {
|
||||
mIntDataCache[i] = new int[mIntCacheColumnSize];
|
||||
}
|
||||
if (mStringDataCache != null) {
|
||||
mStringDataCache[i] = new String[mStringCacheColumnSize];
|
||||
}
|
||||
}
|
||||
|
||||
// Cache at the initialization stage.
|
||||
loadCacheStartingFromPosition(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to load un-cached data with size {@link PAGE_SIZE} starting from given index.
|
||||
*/
|
||||
private void loadCacheStartingFromPosition(int index) {
|
||||
mCursor.moveToPosition(index);
|
||||
for (int row = index; row < (index + PAGE_SIZE) && row < mRowCount; row++) {
|
||||
if (!mCachedRows[row]) {
|
||||
for (int col = 0; col < mColumnCount; col++) {
|
||||
switch (mCursor.getType(col)) {
|
||||
case Cursor.FIELD_TYPE_BLOB:
|
||||
mByteArrayDataCache[row][mByteArrayCacheIndexMap[col]] =
|
||||
mCursor.getBlob(col);
|
||||
break;
|
||||
case Cursor.FIELD_TYPE_FLOAT:
|
||||
mFloatDataCache[row][mFloatCacheIndexMap[col]] = mCursor.getFloat(col);
|
||||
break;
|
||||
case Cursor.FIELD_TYPE_INTEGER:
|
||||
mIntDataCache[row][mIntCacheIndexMap[col]] = mCursor.getInt(col);
|
||||
break;
|
||||
case Cursor.FIELD_TYPE_STRING:
|
||||
mStringDataCache[row][mStringCacheIndexMap[col]] =
|
||||
mCursor.getString(col);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mCachedRows[row] = true;
|
||||
}
|
||||
mCursor.moveToNext();
|
||||
}
|
||||
mLastCachePosition = Math.min(index + PAGE_SIZE, mRowCount) - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMove(int oldPosition, int newPosition) {
|
||||
// If it's a consecutive move and haven't exceeds the threshold, do nothing.
|
||||
if ((newPosition - oldPosition) != 1 ||
|
||||
(newPosition + PAGE_THRESHOLD) <= mLastCachePosition) {
|
||||
loadCacheStartingFromPosition(newPosition);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType(int column) {
|
||||
return mColumnTypes[column];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mRowCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getColumnNames() {
|
||||
return mColumnNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(int column) {
|
||||
return mStringDataCache[mPos][mStringCacheIndexMap[column]];
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort(int column) {
|
||||
return (short) mIntDataCache[mPos][mIntCacheIndexMap[column]];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(int column) {
|
||||
return mIntDataCache[mPos][mIntCacheIndexMap[column]];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(int column) {
|
||||
return mIntDataCache[mPos][mIntCacheIndexMap[column]];
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloat(int column) {
|
||||
return mFloatDataCache[mPos][mFloatCacheIndexMap[column]];
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDouble(int column) {
|
||||
return mFloatDataCache[mPos][mFloatCacheIndexMap[column]];
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getBlob(int column) {
|
||||
return mByteArrayDataCache[mPos][mByteArrayCacheIndexMap[column]];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNull(int column) {
|
||||
return mColumnTypes[column] == Cursor.FIELD_TYPE_NULL;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Copyright (C) 2015 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.tvleanback.data;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.UriMatcher;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Provides access to the video database.
|
||||
*/
|
||||
public class VideoContentProvider extends ContentProvider {
|
||||
private static String TAG = "VideoContentProvider";
|
||||
public static String AUTHORITY = "com.example.android.tvleanback";
|
||||
|
||||
// UriMatcher stuff
|
||||
private static final int SEARCH_SUGGEST = 0;
|
||||
private static final int REFRESH_SHORTCUT = 1;
|
||||
private static final UriMatcher URI_MATCHER = buildUriMatcher();
|
||||
|
||||
private VideoDatabase mVideoDatabase;
|
||||
|
||||
/**
|
||||
* Builds up a UriMatcher for search suggestion and shortcut refresh queries.
|
||||
*/
|
||||
private static UriMatcher buildUriMatcher() {
|
||||
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
// to get suggestions...
|
||||
Log.d(TAG, "suggest_uri_path_query: " + SearchManager.SUGGEST_URI_PATH_QUERY);
|
||||
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGEST);
|
||||
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGEST);
|
||||
return matcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
Log.d(TAG, "onCreate");
|
||||
mVideoDatabase = new VideoDatabase(getContext());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all the video searches and suggestion queries from the Search Manager.
|
||||
* When requesting a specific word, the uri alone is required.
|
||||
* When searching all of the video for matches, the selectionArgs argument must carry
|
||||
* the search query as the first element.
|
||||
* All other arguments are ignored.
|
||||
*/
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
||||
String sortOrder) {
|
||||
// Use the UriMatcher to see what kind of query we have and format the db query accordingly
|
||||
switch (URI_MATCHER.match(uri)) {
|
||||
case SEARCH_SUGGEST:
|
||||
Log.d(TAG, "search suggest: " + selectionArgs[0] + " URI: " + uri);
|
||||
if (selectionArgs == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"selectionArgs must be provided for the Uri: " + uri);
|
||||
}
|
||||
return getSuggestions(selectionArgs[0]);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown Uri: " + uri);
|
||||
}
|
||||
}
|
||||
|
||||
private Cursor getSuggestions(String query) {
|
||||
query = query.toLowerCase();
|
||||
String[] columns = new String[]{
|
||||
BaseColumns._ID,
|
||||
VideoDatabase.KEY_NAME,
|
||||
VideoDatabase.KEY_DESCRIPTION,
|
||||
VideoDatabase.KEY_ICON,
|
||||
VideoDatabase.KEY_DATA_TYPE,
|
||||
VideoDatabase.KEY_IS_LIVE,
|
||||
VideoDatabase.KEY_VIDEO_WIDTH,
|
||||
VideoDatabase.KEY_VIDEO_HEIGHT,
|
||||
VideoDatabase.KEY_AUDIO_CHANNEL_CONFIG,
|
||||
VideoDatabase.KEY_PURCHASE_PRICE,
|
||||
VideoDatabase.KEY_RENTAL_PRICE,
|
||||
VideoDatabase.KEY_RATING_STYLE,
|
||||
VideoDatabase.KEY_RATING_SCORE,
|
||||
VideoDatabase.KEY_PRODUCTION_YEAR,
|
||||
VideoDatabase.KEY_COLUMN_DURATION,
|
||||
VideoDatabase.KEY_ACTION,
|
||||
SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID
|
||||
};
|
||||
return mVideoDatabase.getWordMatch(query, columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is required in order to query the supported types.
|
||||
* It's also useful in our own query() method to determine the type of Uri received.
|
||||
*/
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
switch (URI_MATCHER.match(uri)) {
|
||||
case SEARCH_SUGGEST:
|
||||
return SearchManager.SUGGEST_MIME_TYPE;
|
||||
case REFRESH_SHORTCUT:
|
||||
return SearchManager.SHORTCUT_MIME_TYPE;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown URL " + uri);
|
||||
}
|
||||
}
|
||||
|
||||
// Other required implementations...
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,344 @@
|
|||
/*
|
||||
* Copyright (C) 2015 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.tvleanback.data;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.media.Rating;
|
||||
import android.provider.BaseColumns;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.android.tvleanback.R;
|
||||
import com.example.android.tvleanback.model.Movie;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Contains logic to return specific words from the video database, and
|
||||
* load the video database table when it needs to be created.
|
||||
*/
|
||||
public class VideoDatabase {
|
||||
//The columns we'll include in the video database table
|
||||
public static final String KEY_NAME = SearchManager.SUGGEST_COLUMN_TEXT_1;
|
||||
public static final String KEY_DESCRIPTION = SearchManager.SUGGEST_COLUMN_TEXT_2;
|
||||
public static final String KEY_ICON = SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE;
|
||||
public static final String KEY_DATA_TYPE = SearchManager.SUGGEST_COLUMN_CONTENT_TYPE;
|
||||
public static final String KEY_IS_LIVE = SearchManager.SUGGEST_COLUMN_IS_LIVE;
|
||||
public static final String KEY_VIDEO_WIDTH = SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH;
|
||||
public static final String KEY_VIDEO_HEIGHT = SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT;
|
||||
public static final String KEY_AUDIO_CHANNEL_CONFIG =
|
||||
SearchManager.SUGGEST_COLUMN_AUDIO_CHANNEL_CONFIG;
|
||||
public static final String KEY_PURCHASE_PRICE = SearchManager.SUGGEST_COLUMN_PURCHASE_PRICE;
|
||||
public static final String KEY_RENTAL_PRICE = SearchManager.SUGGEST_COLUMN_RENTAL_PRICE;
|
||||
public static final String KEY_RATING_STYLE = SearchManager.SUGGEST_COLUMN_RATING_STYLE;
|
||||
public static final String KEY_RATING_SCORE = SearchManager.SUGGEST_COLUMN_RATING_SCORE;
|
||||
public static final String KEY_PRODUCTION_YEAR = SearchManager.SUGGEST_COLUMN_PRODUCTION_YEAR;
|
||||
public static final String KEY_COLUMN_DURATION = SearchManager.SUGGEST_COLUMN_DURATION;
|
||||
public static final String KEY_ACTION = SearchManager.SUGGEST_COLUMN_INTENT_ACTION;
|
||||
private static final String TAG = "VideoDatabase";
|
||||
private static final String DATABASE_NAME = "video_database_leanback";
|
||||
private static final String FTS_VIRTUAL_TABLE = "Leanback_table";
|
||||
private static final int DATABASE_VERSION = 2;
|
||||
private static final HashMap<String, String> COLUMN_MAP = buildColumnMap();
|
||||
private static int CARD_WIDTH = 313;
|
||||
private static int CARD_HEIGHT = 176;
|
||||
private final VideoDatabaseOpenHelper mDatabaseOpenHelper;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param context The Context within which to work, used to create the DB
|
||||
*/
|
||||
public VideoDatabase(Context context) {
|
||||
mDatabaseOpenHelper = new VideoDatabaseOpenHelper(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a map for all columns that may be requested, which will be given to the
|
||||
* SQLiteQueryBuilder. This is a good way to define aliases for column names, but must include
|
||||
* all columns, even if the value is the key. This allows the ContentProvider to request
|
||||
* columns w/o the need to know real column names and create the alias itself.
|
||||
*/
|
||||
private static HashMap<String, String> buildColumnMap() {
|
||||
HashMap<String, String> map = new HashMap<String, String>();
|
||||
map.put(KEY_NAME, KEY_NAME);
|
||||
map.put(KEY_DESCRIPTION, KEY_DESCRIPTION);
|
||||
map.put(KEY_ICON, KEY_ICON);
|
||||
map.put(KEY_DATA_TYPE, KEY_DATA_TYPE);
|
||||
map.put(KEY_IS_LIVE, KEY_IS_LIVE);
|
||||
map.put(KEY_VIDEO_WIDTH, KEY_VIDEO_WIDTH);
|
||||
map.put(KEY_VIDEO_HEIGHT, KEY_VIDEO_HEIGHT);
|
||||
map.put(KEY_AUDIO_CHANNEL_CONFIG, KEY_AUDIO_CHANNEL_CONFIG);
|
||||
map.put(KEY_PURCHASE_PRICE, KEY_PURCHASE_PRICE);
|
||||
map.put(KEY_RENTAL_PRICE, KEY_RENTAL_PRICE);
|
||||
map.put(KEY_RATING_STYLE, KEY_RATING_STYLE);
|
||||
map.put(KEY_RATING_SCORE, KEY_RATING_SCORE);
|
||||
map.put(KEY_PRODUCTION_YEAR, KEY_PRODUCTION_YEAR);
|
||||
map.put(KEY_COLUMN_DURATION, KEY_COLUMN_DURATION);
|
||||
map.put(KEY_ACTION, KEY_ACTION);
|
||||
map.put(BaseColumns._ID, "rowid AS " +
|
||||
BaseColumns._ID);
|
||||
map.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, "rowid AS " +
|
||||
SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
|
||||
map.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, "rowid AS " +
|
||||
SearchManager.SUGGEST_COLUMN_SHORTCUT_ID);
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Cursor positioned at the word specified by rowId
|
||||
*
|
||||
* @param rowId id of word to retrieve
|
||||
* @param columns The columns to include, if null then all are included
|
||||
* @return Cursor positioned to matching word, or null if not found.
|
||||
*/
|
||||
public Cursor getWord(String rowId, String[] columns) {
|
||||
/* This builds a query that looks like:
|
||||
* SELECT <columns> FROM <table> WHERE rowid = <rowId>
|
||||
*/
|
||||
String selection = "rowid = ?";
|
||||
String[] selectionArgs = new String[]{rowId};
|
||||
|
||||
return query(selection, selectionArgs, columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Cursor over all words that match the first letter of the given query
|
||||
*
|
||||
* @param query The string to search for
|
||||
* @param columns The columns to include, if null then all are included
|
||||
* @return Cursor over all words that match, or null if none found.
|
||||
*/
|
||||
public Cursor getWordMatch(String query, String[] columns) {
|
||||
/* This builds a query that looks like:
|
||||
* SELECT <columns> FROM <table> WHERE <KEY_WORD> MATCH 'query*'
|
||||
* which is an FTS3 search for the query text (plus a wildcard) inside the word column.
|
||||
*
|
||||
* - "rowid" is the unique id for all rows but we need this value for the "_id" column in
|
||||
* order for the Adapters to work, so the columns need to make "_id" an alias for "rowid"
|
||||
* - "rowid" also needs to be used by the SUGGEST_COLUMN_INTENT_DATA alias in order
|
||||
* for suggestions to carry the proper intent data.SearchManager
|
||||
* These aliases are defined in the VideoProvider when queries are made.
|
||||
* - This can be revised to also search the definition text with FTS3 by changing
|
||||
* the selection clause to use FTS_VIRTUAL_TABLE instead of KEY_WORD (to search across
|
||||
* the entire table, but sorting the relevance could be difficult.
|
||||
*/
|
||||
String selection = KEY_NAME + " MATCH ?";
|
||||
String[] selectionArgs = new String[]{query + "*"};
|
||||
|
||||
return query(selection, selectionArgs, columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a database query.
|
||||
*
|
||||
* @param selection The selection clause
|
||||
* @param selectionArgs Selection arguments for "?" components in the selection
|
||||
* @param columns The columns to return
|
||||
* @return A Cursor over all rows matching the query
|
||||
*/
|
||||
private Cursor query(String selection, String[] selectionArgs, String[] columns) {
|
||||
/* The SQLiteBuilder provides a map for all possible columns requested to
|
||||
* actual columns in the database, creating a simple column alias mechanism
|
||||
* by which the ContentProvider does not need to know the real column names
|
||||
*/
|
||||
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
|
||||
builder.setTables(FTS_VIRTUAL_TABLE);
|
||||
builder.setProjectionMap(COLUMN_MAP);
|
||||
|
||||
Cursor cursor = new PaginatedCursor(builder.query(mDatabaseOpenHelper.getReadableDatabase(),
|
||||
columns, selection, selectionArgs, null, null, null));
|
||||
|
||||
if (cursor == null) {
|
||||
return null;
|
||||
} else if (!cursor.moveToFirst()) {
|
||||
cursor.close();
|
||||
return null;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates/opens the database.
|
||||
*/
|
||||
private static class VideoDatabaseOpenHelper extends SQLiteOpenHelper {
|
||||
|
||||
private final Context mHelperContext;
|
||||
private SQLiteDatabase mDatabase;
|
||||
|
||||
VideoDatabaseOpenHelper(Context context) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
mHelperContext = context;
|
||||
}
|
||||
|
||||
/* Note that FTS3 does not support column constraints and thus, you cannot
|
||||
* declare a primary key. However, "rowid" is automatically used as a unique
|
||||
* identifier, so when making requests, we will use "_id" as an alias for "rowid"
|
||||
*/
|
||||
private static final String FTS_TABLE_CREATE =
|
||||
"CREATE VIRTUAL TABLE " + FTS_VIRTUAL_TABLE +
|
||||
" USING fts3 (" +
|
||||
KEY_NAME + ", " +
|
||||
KEY_DESCRIPTION + "," +
|
||||
KEY_ICON + "," +
|
||||
KEY_DATA_TYPE + "," +
|
||||
KEY_IS_LIVE + "," +
|
||||
KEY_VIDEO_WIDTH + "," +
|
||||
KEY_VIDEO_HEIGHT + "," +
|
||||
KEY_AUDIO_CHANNEL_CONFIG + "," +
|
||||
KEY_PURCHASE_PRICE + "," +
|
||||
KEY_RENTAL_PRICE + "," +
|
||||
KEY_RATING_STYLE + "," +
|
||||
KEY_RATING_SCORE + "," +
|
||||
KEY_PRODUCTION_YEAR + "," +
|
||||
KEY_COLUMN_DURATION + "," +
|
||||
KEY_ACTION + ");";
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
mDatabase = db;
|
||||
mDatabase.execSQL(FTS_TABLE_CREATE);
|
||||
loadDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a thread to load the database table with words
|
||||
*/
|
||||
private void loadDatabase() {
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
loadMovies();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void loadMovies() throws IOException {
|
||||
Log.d(TAG, "Loading movies...");
|
||||
|
||||
HashMap<String, List<Movie>> movies = null;
|
||||
try {
|
||||
VideoProvider.setContext(mHelperContext);
|
||||
movies = VideoProvider.buildMedia(mHelperContext,
|
||||
mHelperContext.getResources().getString(R.string.catalog_url));
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "JSon Exception when loading movie", e);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, List<Movie>> entry : movies.entrySet()) {
|
||||
List<Movie> list = entry.getValue();
|
||||
for (Movie movie : list) {
|
||||
long id = addMovie(movie);
|
||||
if (id < 0) {
|
||||
Log.e(TAG, "unable to add movie: " + movie.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
// add dummy movies to illustrate action deep link in search detail
|
||||
// Android TV Search requires that the media’s title, MIME type, production year,
|
||||
// and duration all match exactly to those found from Google’s servers.
|
||||
addMovieForDeepLink(mHelperContext.getString(R.string.noah_title),
|
||||
mHelperContext.getString(R.string.noah_description),
|
||||
R.drawable.noah,
|
||||
8280000,
|
||||
"2014");
|
||||
addMovieForDeepLink(mHelperContext.getString(R.string.dragon2_title),
|
||||
mHelperContext.getString(R.string.dragon2_description),
|
||||
R.drawable.dragon2,
|
||||
6300000,
|
||||
"2014");
|
||||
addMovieForDeepLink(mHelperContext.getString(R.string.maleficent_title),
|
||||
mHelperContext.getString(R.string.maleficent_description),
|
||||
R.drawable.maleficent,
|
||||
5820000,
|
||||
"2014");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a movie to the database.
|
||||
*
|
||||
* @return rowId or -1 if failed
|
||||
*/
|
||||
public long addMovie(Movie movie) {
|
||||
ContentValues initialValues = new ContentValues();
|
||||
initialValues.put(KEY_NAME, movie.getTitle());
|
||||
initialValues.put(KEY_DESCRIPTION, movie.getDescription());
|
||||
initialValues.put(KEY_ICON, movie.getCardImageUrl());
|
||||
initialValues.put(KEY_DATA_TYPE, "video/mp4");
|
||||
initialValues.put(KEY_IS_LIVE, false);
|
||||
initialValues.put(KEY_VIDEO_WIDTH, CARD_WIDTH);
|
||||
initialValues.put(KEY_VIDEO_HEIGHT, CARD_HEIGHT);
|
||||
initialValues.put(KEY_AUDIO_CHANNEL_CONFIG, "2.0");
|
||||
initialValues.put(KEY_PURCHASE_PRICE, mHelperContext.getString(R.string.buy_2));
|
||||
initialValues.put(KEY_RENTAL_PRICE, mHelperContext.getString(R.string.rent_2));
|
||||
initialValues.put(KEY_RATING_STYLE, Rating.RATING_5_STARS);
|
||||
initialValues.put(KEY_RATING_SCORE, 3.5f);
|
||||
initialValues.put(KEY_PRODUCTION_YEAR, 2014);
|
||||
initialValues.put(KEY_COLUMN_DURATION, 0);
|
||||
initialValues.put(KEY_ACTION, mHelperContext.getString(R.string.global_search));
|
||||
return mDatabase.insert(FTS_VIRTUAL_TABLE, null, initialValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an entry to the database for dummy deep link.
|
||||
*
|
||||
* @return rowId or -1 if failed
|
||||
*/
|
||||
public long addMovieForDeepLink(String title, String description, int icon, long duration, String production_year) {
|
||||
ContentValues initialValues = new ContentValues();
|
||||
initialValues.put(KEY_NAME, title);
|
||||
initialValues.put(KEY_DESCRIPTION, description);
|
||||
initialValues.put(KEY_ICON, icon);
|
||||
initialValues.put(KEY_DATA_TYPE, "video/mp4");
|
||||
initialValues.put(KEY_IS_LIVE, false);
|
||||
initialValues.put(KEY_VIDEO_WIDTH, 1280);
|
||||
initialValues.put(KEY_VIDEO_HEIGHT, 720);
|
||||
initialValues.put(KEY_AUDIO_CHANNEL_CONFIG, "2.0");
|
||||
initialValues.put(KEY_PURCHASE_PRICE, "Free");
|
||||
initialValues.put(KEY_RENTAL_PRICE, "Free");
|
||||
initialValues.put(KEY_RATING_STYLE, Rating.RATING_5_STARS);
|
||||
initialValues.put(KEY_RATING_SCORE, 3.5f);
|
||||
initialValues.put(KEY_PRODUCTION_YEAR, production_year);
|
||||
initialValues.put(KEY_COLUMN_DURATION, duration);
|
||||
return mDatabase.insert(FTS_VIRTUAL_TABLE, null, initialValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
|
||||
+ newVersion + ", which will destroy all old data");
|
||||
db.execSQL("DROP TABLE IF EXISTS " + FTS_VIRTUAL_TABLE);
|
||||
onCreate(db);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (C) 2015 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.tvleanback.data;
|
||||
|
||||
import android.content.AsyncTaskLoader;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.android.tvleanback.model.Movie;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* This class asynchronously loads videos from a backend
|
||||
*/
|
||||
public class VideoItemLoader extends AsyncTaskLoader<HashMap<String, List<Movie>>> {
|
||||
|
||||
private static final String TAG = "VideoItemLoader";
|
||||
private final String mUrl;
|
||||
private Context mContext;
|
||||
|
||||
public VideoItemLoader(Context context, String url) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mUrl = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<String, List<Movie>> loadInBackground() {
|
||||
try {
|
||||
return VideoProvider.buildMedia(mContext, mUrl);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to fetch media data", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
super.onStartLoading();
|
||||
forceLoad();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request to stop the Loader.
|
||||
*/
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
// Attempt to cancel the current load task if possible.
|
||||
cancelLoad();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* Copyright (C) 2015 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.tvleanback.data;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.android.tvleanback.R;
|
||||
import com.example.android.tvleanback.model.Movie;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* This class loads videos from a backend and saves them into a HashMap
|
||||
*/
|
||||
public class VideoProvider {
|
||||
|
||||
private static final String TAG = "VideoProvider";
|
||||
private static String TAG_MEDIA = "videos";
|
||||
private static String TAG_GOOGLE_VIDEOS = "googlevideos";
|
||||
private static String TAG_CATEGORY = "category";
|
||||
private static String TAG_STUDIO = "studio";
|
||||
private static String TAG_SOURCES = "sources";
|
||||
private static String TAG_DESCRIPTION = "description";
|
||||
private static String TAG_CARD_THUMB = "card";
|
||||
private static String TAG_BACKGROUND = "background";
|
||||
private static String TAG_TITLE = "title";
|
||||
|
||||
private static HashMap<String, List<Movie>> sMovieList;
|
||||
private static Context sContext;
|
||||
private static String sPrefixUrl;
|
||||
|
||||
public static void setContext(Context context) {
|
||||
if (sContext == null)
|
||||
sContext = context;
|
||||
}
|
||||
|
||||
public static HashMap<String, List<Movie>> getMovieList() {
|
||||
return sMovieList;
|
||||
}
|
||||
|
||||
public static HashMap<String, List<Movie>> buildMedia(Context ctx, String url)
|
||||
throws JSONException {
|
||||
if (null != sMovieList) {
|
||||
return sMovieList;
|
||||
}
|
||||
sMovieList = new HashMap<String, List<Movie>>();
|
||||
|
||||
JSONObject jsonObj = new VideoProvider().parseUrl(url);
|
||||
JSONArray categories = jsonObj.getJSONArray(TAG_GOOGLE_VIDEOS);
|
||||
if (null != categories) {
|
||||
Log.d(TAG, "category #: " + categories.length());
|
||||
String title = new String();
|
||||
String videoUrl = new String();
|
||||
String bgImageUrl = new String();
|
||||
String cardImageUrl = new String();
|
||||
String studio = new String();
|
||||
for (int i = 0; i < categories.length(); i++) {
|
||||
JSONObject category = categories.getJSONObject(i);
|
||||
String category_name = category.getString(TAG_CATEGORY);
|
||||
JSONArray videos = category.getJSONArray(TAG_MEDIA);
|
||||
Log.d(TAG,
|
||||
"category: " + i + " Name:" + category_name + " video length: "
|
||||
+ videos.length());
|
||||
List<Movie> categoryList = new ArrayList<Movie>();
|
||||
if (null != videos) {
|
||||
for (int j = 0; j < videos.length(); j++) {
|
||||
JSONObject video = videos.getJSONObject(j);
|
||||
String description = video.getString(TAG_DESCRIPTION);
|
||||
JSONArray videoUrls = video.getJSONArray(TAG_SOURCES);
|
||||
if (null == videoUrls || videoUrls.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
title = video.getString(TAG_TITLE);
|
||||
videoUrl = getVideoPrefix(category_name, videoUrls.getString(0));
|
||||
bgImageUrl = getThumbPrefix(category_name, title,
|
||||
video.getString(TAG_BACKGROUND));
|
||||
cardImageUrl = getThumbPrefix(category_name, title,
|
||||
video.getString(TAG_CARD_THUMB));
|
||||
studio = video.getString(TAG_STUDIO);
|
||||
categoryList.add(buildMovieInfo(category_name, title, description, studio,
|
||||
videoUrl, cardImageUrl,
|
||||
bgImageUrl));
|
||||
}
|
||||
sMovieList.put(category_name, categoryList);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sMovieList;
|
||||
}
|
||||
|
||||
private static Movie buildMovieInfo(String category,
|
||||
String title,
|
||||
String description,
|
||||
String studio,
|
||||
String videoUrl,
|
||||
String cardImageUrl,
|
||||
String bgImageUrl) {
|
||||
Movie movie = new Movie();
|
||||
movie.setId(Movie.getCount());
|
||||
Movie.incrementCount();
|
||||
movie.setTitle(title);
|
||||
movie.setDescription(description);
|
||||
movie.setStudio(studio);
|
||||
movie.setCategory(category);
|
||||
movie.setCardImageUrl(cardImageUrl);
|
||||
movie.setBackgroundImageUrl(bgImageUrl);
|
||||
movie.setVideoUrl(videoUrl);
|
||||
|
||||
return movie;
|
||||
}
|
||||
|
||||
private static String getVideoPrefix(String category, String videoUrl) {
|
||||
String ret = "";
|
||||
ret = sPrefixUrl + category.replace(" ", "%20") + '/' +
|
||||
videoUrl.replace(" ", "%20");
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static String getThumbPrefix(String category, String title, String imageUrl) {
|
||||
String ret = "";
|
||||
|
||||
ret = sPrefixUrl + category.replace(" ", "%20") + '/' +
|
||||
title.replace(" ", "%20") + '/' +
|
||||
imageUrl.replace(" ", "%20");
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected JSONObject parseUrl(String urlString) {
|
||||
Log.d(TAG, "Parse URL: " + urlString);
|
||||
InputStream is = null;
|
||||
|
||||
sPrefixUrl = sContext.getResources().getString(R.string.prefix_url);
|
||||
|
||||
try {
|
||||
java.net.URL url = new java.net.URL(urlString);
|
||||
URLConnection urlConnection = url.openConnection();
|
||||
is = new BufferedInputStream(urlConnection.getInputStream());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(
|
||||
urlConnection.getInputStream(), "iso-8859-1"), 8);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line = null;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
String json = sb.toString();
|
||||
return new JSONObject(json);
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "Failed to parse the json for media list", e);
|
||||
return null;
|
||||
} finally {
|
||||
if (null != is) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "JSON feed closed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
* Copyright (C) 2015 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.tvleanback.model;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
/*
|
||||
* Movie class represents video entity with title, description, image thumbs and video url.
|
||||
*/
|
||||
public class Movie implements Parcelable {
|
||||
private static final String TAG = "Movie";
|
||||
static final long serialVersionUID = 727566175075960653L;
|
||||
private static int sCount = 0;
|
||||
private String mId;
|
||||
private String mTitle;
|
||||
private String mDescription;
|
||||
private String mBgImageUrl;
|
||||
private String mCardImageUrl;
|
||||
private String mVideoUrl;
|
||||
private String mStudio;
|
||||
private String mCategory;
|
||||
|
||||
public Movie() {
|
||||
|
||||
}
|
||||
|
||||
public Movie(Parcel in){
|
||||
String[] data = new String[8];
|
||||
|
||||
in.readStringArray(data);
|
||||
mId = data[0];
|
||||
mTitle = data[1];
|
||||
mDescription = data[2];
|
||||
mBgImageUrl = data[3];
|
||||
mCardImageUrl = data[4];
|
||||
mVideoUrl = data[5];
|
||||
mStudio = data[6];
|
||||
mCategory = data[7];
|
||||
}
|
||||
|
||||
public static String getCount() {
|
||||
return Integer.toString(sCount);
|
||||
}
|
||||
|
||||
public static void incrementCount() {
|
||||
sCount++;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
mId = id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
mTitle = title;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return mDescription;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
mDescription = description;
|
||||
}
|
||||
|
||||
public String getStudio() {
|
||||
return mStudio;
|
||||
}
|
||||
|
||||
public void setStudio(String studio) {
|
||||
mStudio = studio;
|
||||
}
|
||||
|
||||
public String getVideoUrl() {
|
||||
return mVideoUrl;
|
||||
}
|
||||
|
||||
public void setVideoUrl(String videoUrl) {
|
||||
mVideoUrl = videoUrl;
|
||||
}
|
||||
|
||||
public String getBackgroundImageUrl() {
|
||||
return mBgImageUrl;
|
||||
}
|
||||
|
||||
public void setBackgroundImageUrl(String bgImageUrl) {
|
||||
mBgImageUrl = bgImageUrl;
|
||||
}
|
||||
|
||||
public String getCardImageUrl() {
|
||||
return mCardImageUrl;
|
||||
}
|
||||
|
||||
public void setCardImageUrl(String cardImageUrl) {
|
||||
mCardImageUrl = cardImageUrl;
|
||||
}
|
||||
|
||||
public String getCategory() {
|
||||
return mCategory;
|
||||
}
|
||||
|
||||
public void setCategory(String category) {
|
||||
mCategory = category;
|
||||
}
|
||||
|
||||
public URI getBackgroundImageURI() {
|
||||
try {
|
||||
return new URI(getBackgroundImageUrl());
|
||||
} catch (URISyntaxException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeStringArray(new String[] {mId,
|
||||
mTitle,
|
||||
mDescription,
|
||||
mBgImageUrl,
|
||||
mCardImageUrl,
|
||||
mVideoUrl,
|
||||
mStudio,
|
||||
mCategory});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder(200);
|
||||
sb.append("Movie{");
|
||||
sb.append("mId=" + mId);
|
||||
sb.append(", mTitle='" + mTitle + '\'');
|
||||
sb.append(", mVideoUrl='" + mVideoUrl + '\'');
|
||||
sb.append(", backgroundImageUrl='" + mBgImageUrl + '\'');
|
||||
sb.append(", backgroundImageURI='" + getBackgroundImageURI().toString() + '\'');
|
||||
sb.append(", mCardImageUrl='" + mCardImageUrl + '\'');
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
|
||||
public Movie createFromParcel(Parcel in) {
|
||||
return new Movie(in);
|
||||
}
|
||||
|
||||
public Movie[] newArray(int size) {
|
||||
return new Movie[size];
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright (C) 2015 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.tvleanback.presenter;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v17.leanback.widget.ImageCardView;
|
||||
import android.support.v17.leanback.widget.Presenter;
|
||||
import android.util.Log;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.example.android.tvleanback.R;
|
||||
import com.example.android.tvleanback.model.Movie;
|
||||
|
||||
/*
|
||||
* A CardPresenter is used to generate Views and bind Objects to them on demand.
|
||||
* It contains an Image CardView
|
||||
*/
|
||||
public class CardPresenter extends Presenter {
|
||||
private static final String TAG = "CardPresenter";
|
||||
|
||||
private static int CARD_WIDTH = 313;
|
||||
private static int CARD_HEIGHT = 176;
|
||||
private static int sSelectedBackgroundColor;
|
||||
private static int sDefaultBackgroundColor;
|
||||
private Drawable mDefaultCardImage;
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent) {
|
||||
Log.d(TAG, "onCreateViewHolder");
|
||||
|
||||
sDefaultBackgroundColor = parent.getResources().getColor(R.color.default_background);
|
||||
sSelectedBackgroundColor = parent.getResources().getColor(R.color.selected_background);
|
||||
mDefaultCardImage = parent.getResources().getDrawable(R.drawable.movie);
|
||||
|
||||
ImageCardView cardView = new ImageCardView(parent.getContext()) {
|
||||
@Override
|
||||
public void setSelected(boolean selected) {
|
||||
updateCardBackgroundColor(this, selected);
|
||||
super.setSelected(selected);
|
||||
}
|
||||
};
|
||||
|
||||
cardView.setFocusable(true);
|
||||
cardView.setFocusableInTouchMode(true);
|
||||
updateCardBackgroundColor(cardView, false);
|
||||
return new ViewHolder(cardView);
|
||||
}
|
||||
|
||||
private static void updateCardBackgroundColor(ImageCardView view, boolean selected) {
|
||||
int color = selected ? sSelectedBackgroundColor : sDefaultBackgroundColor;
|
||||
// Both background colors should be set because the view's background is temporarily visible
|
||||
// during animations.
|
||||
view.setBackgroundColor(color);
|
||||
view.findViewById(R.id.info_field).setBackgroundColor(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
|
||||
Movie movie = (Movie) item;
|
||||
ImageCardView cardView = (ImageCardView) viewHolder.view;
|
||||
|
||||
Log.d(TAG, "onBindViewHolder");
|
||||
if (movie.getCardImageUrl() != null) {
|
||||
cardView.setTitleText(movie.getTitle());
|
||||
cardView.setContentText(movie.getStudio());
|
||||
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT);
|
||||
Glide.with(viewHolder.view.getContext())
|
||||
.load(movie.getCardImageUrl())
|
||||
.centerCrop()
|
||||
.error(mDefaultCardImage)
|
||||
.into(cardView.getMainImageView());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
|
||||
Log.d(TAG, "onUnbindViewHolder");
|
||||
ImageCardView cardView = (ImageCardView) viewHolder.view;
|
||||
// Remove references to images so that the garbage collector can free up memory
|
||||
cardView.setBadgeImage(null);
|
||||
cardView.setMainImage(null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (C) 2015 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.tvleanback.presenter;
|
||||
|
||||
import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
|
||||
|
||||
import com.example.android.tvleanback.model.Movie;
|
||||
|
||||
public class DetailsDescriptionPresenter extends AbstractDetailsDescriptionPresenter {
|
||||
|
||||
@Override
|
||||
protected void onBindDescription(ViewHolder viewHolder, Object item) {
|
||||
Movie movie = (Movie) item;
|
||||
|
||||
if (movie != null) {
|
||||
viewHolder.getTitle().setText(movie.getTitle());
|
||||
viewHolder.getSubtitle().setText(movie.getStudio());
|
||||
viewHolder.getBody().setText(movie.getDescription());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (C) 2015 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.tvleanback.presenter;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.support.v17.leanback.widget.Presenter;
|
||||
import android.view.Gravity;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.example.android.tvleanback.R;
|
||||
import com.example.android.tvleanback.ui.MainFragment;
|
||||
|
||||
public class GridItemPresenter extends Presenter {
|
||||
private static int GRID_ITEM_WIDTH = 200;
|
||||
private static int GRID_ITEM_HEIGHT = 200;
|
||||
|
||||
private MainFragment mainFragment;
|
||||
|
||||
public GridItemPresenter(MainFragment mainFragment) {
|
||||
this.mainFragment = mainFragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent) {
|
||||
TextView view = new TextView(parent.getContext());
|
||||
view.setLayoutParams(new ViewGroup.LayoutParams(GRID_ITEM_WIDTH, GRID_ITEM_HEIGHT));
|
||||
view.setFocusable(true);
|
||||
view.setFocusableInTouchMode(true);
|
||||
view.setBackgroundColor(mainFragment.getResources().getColor(R.color.default_background));
|
||||
view.setTextColor(Color.WHITE);
|
||||
view.setGravity(Gravity.CENTER);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder viewHolder, Object item) {
|
||||
((TextView) viewHolder.view).setText((String) item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnbindViewHolder(ViewHolder viewHolder) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (C) 2015 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.tvleanback.recommendation;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
/*
|
||||
* This class extends BroadCastReceiver and publishes recommendations on bootup
|
||||
*/
|
||||
public class BootupActivity extends BroadcastReceiver {
|
||||
private static final String TAG = "BootupActivity";
|
||||
|
||||
private static final long INITIAL_DELAY = 5000;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d(TAG, "BootupActivity initiated");
|
||||
if (intent.getAction().endsWith(Intent.ACTION_BOOT_COMPLETED)) {
|
||||
scheduleRecommendationUpdate(context);
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleRecommendationUpdate(Context context) {
|
||||
Log.d(TAG, "Scheduling recommendations update");
|
||||
|
||||
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
Intent recommendationIntent = new Intent(context, UpdateRecommendationsService.class);
|
||||
PendingIntent alarmIntent = PendingIntent.getService(context, 0, recommendationIntent, 0);
|
||||
|
||||
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
||||
INITIAL_DELAY,
|
||||
AlarmManager.INTERVAL_HALF_HOUR,
|
||||
alarmIntent);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
/*
|
||||
* Copyright (C) 2015 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.tvleanback.recommendation;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.android.tvleanback.R;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/*
|
||||
* This class builds recommendations as notifications with videos as inputs.
|
||||
*/
|
||||
public class RecommendationBuilder {
|
||||
private static final String TAG = "RecommendationBuilder";
|
||||
private static final String
|
||||
BACKGROUND_URI_PREFIX = "content://com.example.android.tvleanback.recommendation/";
|
||||
|
||||
private Context mContext;
|
||||
|
||||
private int mId;
|
||||
private int mPriority;
|
||||
private int mSmallIcon;
|
||||
private String mTitle;
|
||||
private String mDescription;
|
||||
private Bitmap mBitmap;
|
||||
private String mBackgroundUri;
|
||||
private String mGroupKey;
|
||||
private String mSort;
|
||||
private PendingIntent mIntent;
|
||||
|
||||
public RecommendationBuilder() {
|
||||
}
|
||||
|
||||
public RecommendationBuilder setContext(Context context) {
|
||||
mContext = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RecommendationBuilder setId(int id) {
|
||||
mId = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RecommendationBuilder setPriority(int priority) {
|
||||
mPriority = priority;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RecommendationBuilder setTitle(String title) {
|
||||
mTitle = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RecommendationBuilder setDescription(String description) {
|
||||
mDescription = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RecommendationBuilder setBitmap(Bitmap bitmap) {
|
||||
mBitmap = bitmap;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RecommendationBuilder setBackground(String uri) {
|
||||
mBackgroundUri = uri;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RecommendationBuilder setIntent(PendingIntent intent) {
|
||||
mIntent = intent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RecommendationBuilder setSmallIcon(int resourceId) {
|
||||
mSmallIcon = resourceId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Notification build() {
|
||||
|
||||
Bundle extras = new Bundle();
|
||||
File bitmapFile = getNotificationBackground(mContext, mId);
|
||||
|
||||
if (mBackgroundUri != null) {
|
||||
extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI,
|
||||
Uri.parse(BACKGROUND_URI_PREFIX + Integer.toString(mId)).toString());
|
||||
}
|
||||
|
||||
// the following simulates group assignment into "Top", "Middle", "Bottom"
|
||||
// by checking mId and similarly sort order
|
||||
mGroupKey = (mId < 3) ? "Top" : (mId < 5) ? "Middle" : "Bottom";
|
||||
mSort = (mId < 3) ? "1.0" : (mId < 5) ? "0.7" : "0.3";
|
||||
|
||||
// save bitmap into files for content provider to serve later
|
||||
try {
|
||||
bitmapFile.createNewFile();
|
||||
FileOutputStream fOut = new FileOutputStream(bitmapFile);
|
||||
mBitmap.compress(Bitmap.CompressFormat.PNG, 85, fOut);
|
||||
fOut.flush();
|
||||
fOut.close();
|
||||
} catch (IOException ioe) {
|
||||
Log.d(TAG, "Exception caught writing bitmap to file!", ioe);
|
||||
}
|
||||
|
||||
Notification notification = new NotificationCompat.BigPictureStyle(
|
||||
new NotificationCompat.Builder(mContext)
|
||||
.setAutoCancel(true)
|
||||
.setContentTitle(mTitle)
|
||||
.setContentText(mDescription)
|
||||
.setPriority(mPriority)
|
||||
.setLocalOnly(true)
|
||||
.setOngoing(true)
|
||||
/*
|
||||
groupKey (optional): Can be used to group together recommendations, so
|
||||
they are ranked by the launcher as a separate group. Can be useful if the
|
||||
application has different sources for recommendations, like "trending",
|
||||
"subscriptions", and "new music" categories for YouTube, where the user can
|
||||
be more interested in recommendations from one group than another.
|
||||
*/
|
||||
.setGroup(mGroupKey)
|
||||
/*
|
||||
sortKey (optional): A float number between 0.0 and 1.0, used to indicate
|
||||
the relative importance (and sort order) of a single recommendation within
|
||||
its specified group. The recommendations will be ordered in decreasing
|
||||
order of importance within a given group.
|
||||
*/
|
||||
.setSortKey(mSort)
|
||||
.setColor(mContext.getResources().getColor(R.color.fastlane_background))
|
||||
.setCategory(Notification.CATEGORY_RECOMMENDATION)
|
||||
.setLargeIcon(mBitmap)
|
||||
.setSmallIcon(mSmallIcon)
|
||||
.setContentIntent(mIntent)
|
||||
.setExtras(extras))
|
||||
.build();
|
||||
|
||||
Log.d(TAG, "Building notification - " + this.toString());
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RecommendationBuilder{" +
|
||||
", mId=" + mId +
|
||||
", mPriority=" + mPriority +
|
||||
", mSmallIcon=" + mSmallIcon +
|
||||
", mTitle='" + mTitle + '\'' +
|
||||
", mDescription='" + mDescription + '\'' +
|
||||
", mBitmap='" + mBitmap + '\'' +
|
||||
", mBackgroundUri='" + mBackgroundUri + '\'' +
|
||||
", mIntent=" + mIntent +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static class RecommendationBackgroundContentProvider extends ContentProvider {
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
||||
String sortOrder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
/*
|
||||
* content provider serving files that are saved locally when recommendations are built
|
||||
*/
|
||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||
int backgroundId = Integer.parseInt(uri.getLastPathSegment());
|
||||
File bitmapFile = getNotificationBackground(getContext(), backgroundId);
|
||||
return ParcelFileDescriptor.open(bitmapFile, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
}
|
||||
}
|
||||
|
||||
private static File getNotificationBackground(Context context, int notificationId) {
|
||||
return new File(context.getCacheDir(), "tmp" + Integer.toString(notificationId) + ".png");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright (C) 2015 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.tvleanback.recommendation;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.TaskStackBuilder;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.util.Log;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.example.android.tvleanback.R;
|
||||
import com.example.android.tvleanback.data.VideoProvider;
|
||||
import com.example.android.tvleanback.model.Movie;
|
||||
import com.example.android.tvleanback.ui.MovieDetailsActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
/*
|
||||
* This class builds up to MAX_RECOMMMENDATIONS of recommendations and defines what happens
|
||||
* when they're clicked from Recommendations section on Home screen
|
||||
*/
|
||||
public class UpdateRecommendationsService extends IntentService {
|
||||
private static final String TAG = "RecommendationsService";
|
||||
private static final int MAX_RECOMMENDATIONS = 3;
|
||||
|
||||
private static final int CARD_WIDTH = 313;
|
||||
private static final int CARD_HEIGHT = 176;
|
||||
|
||||
private NotificationManager mNotificationManager;
|
||||
|
||||
public UpdateRecommendationsService() {
|
||||
super(TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
Log.d(TAG, "Updating recommendation cards");
|
||||
HashMap<String, List<Movie>> recommendations = VideoProvider.getMovieList();
|
||||
if (recommendations == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mNotificationManager == null) {
|
||||
mNotificationManager = (NotificationManager) getApplicationContext()
|
||||
.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
}
|
||||
|
||||
RecommendationBuilder builder = new RecommendationBuilder()
|
||||
.setContext(getApplicationContext())
|
||||
.setSmallIcon(R.drawable.videos_by_google_icon);
|
||||
|
||||
// flatten to list
|
||||
List flattenedRecommendations = new ArrayList();
|
||||
for (Map.Entry<String, List<Movie>> entry : recommendations.entrySet()) {
|
||||
for (Movie movie : entry.getValue()) {
|
||||
Log.d(TAG, "Recommendation - " + movie.getTitle());
|
||||
flattenedRecommendations.add(movie);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.shuffle(flattenedRecommendations);
|
||||
Movie movie;
|
||||
for (int i = 0; i < flattenedRecommendations.size() && i < MAX_RECOMMENDATIONS; i++) {
|
||||
movie = (Movie) flattenedRecommendations.get(i);
|
||||
final RecommendationBuilder notificationBuilder = builder
|
||||
.setBackground(movie.getCardImageUrl())
|
||||
.setId(i+1)
|
||||
.setPriority(MAX_RECOMMENDATIONS - i - 1)
|
||||
.setTitle(movie.getTitle())
|
||||
.setDescription(getString(R.string.popular_header))
|
||||
.setIntent(buildPendingIntent(movie, i + 1));
|
||||
|
||||
try {
|
||||
Bitmap bitmap = Glide.with(getApplicationContext())
|
||||
.load(movie.getCardImageUrl())
|
||||
.asBitmap()
|
||||
.into(CARD_WIDTH, CARD_HEIGHT) // Only use for synchronous .get()
|
||||
.get();
|
||||
notificationBuilder.setBitmap(bitmap);
|
||||
Notification notification = notificationBuilder.build();
|
||||
mNotificationManager.notify(i + 1, notification);
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
Log.e(TAG, "Could not create recommendation: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PendingIntent buildPendingIntent(Movie movie, int id) {
|
||||
Intent detailsIntent = new Intent(this, MovieDetailsActivity.class);
|
||||
detailsIntent.putExtra(MovieDetailsActivity.MOVIE, movie);
|
||||
detailsIntent.putExtra(MovieDetailsActivity.NOTIFICATION_ID, id);
|
||||
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
|
||||
stackBuilder.addParentStack(MovieDetailsActivity.class);
|
||||
stackBuilder.addNextIntent(detailsIntent);
|
||||
// Ensure a unique PendingIntents, otherwise all recommendations end up with the same
|
||||
// PendingIntent
|
||||
detailsIntent.setAction(movie.getId());
|
||||
|
||||
return stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.tvleanback.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import com.example.android.tvleanback.R;
|
||||
|
||||
/*
|
||||
* BrowseErrorActivity shows how to use ErrorFragment
|
||||
*/
|
||||
public class BrowseErrorActivity extends Activity {
|
||||
private static int TIMER_DELAY = 3000;
|
||||
private static int SPINNER_WIDTH = 100;
|
||||
private static int SPINNER_HEIGHT = 100;
|
||||
|
||||
private ErrorFragment mErrorFragment;
|
||||
private SpinnerFragment mSpinnerFragment;
|
||||
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.main);
|
||||
|
||||
testError();
|
||||
}
|
||||
|
||||
private void testError() {
|
||||
mErrorFragment = new ErrorFragment();
|
||||
getFragmentManager().beginTransaction().add(R.id.main_frame, mErrorFragment).commit();
|
||||
|
||||
mSpinnerFragment = new SpinnerFragment();
|
||||
getFragmentManager().beginTransaction().add(R.id.main_frame, mSpinnerFragment).commit();
|
||||
|
||||
final Handler handler = new Handler();
|
||||
handler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getFragmentManager().beginTransaction().remove(mSpinnerFragment).commit();
|
||||
mErrorFragment.setErrorContent();
|
||||
}
|
||||
}, TIMER_DELAY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSearchRequested() {
|
||||
startActivity(new Intent(this, SearchActivity.class));
|
||||
return true;
|
||||
}
|
||||
|
||||
static public class SpinnerFragment extends Fragment {
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
ProgressBar progressBar = new ProgressBar(container.getContext());
|
||||
if (container instanceof FrameLayout) {
|
||||
FrameLayout.LayoutParams layoutParams =
|
||||
new FrameLayout.LayoutParams(SPINNER_WIDTH, SPINNER_HEIGHT, Gravity.CENTER);
|
||||
progressBar.setLayoutParams(layoutParams);
|
||||
}
|
||||
return progressBar;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.tvleanback.ui;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import com.example.android.tvleanback.R;
|
||||
|
||||
/*
|
||||
* This class demonstrates how to extend ErrorFragment
|
||||
*/
|
||||
public class ErrorFragment extends android.support.v17.leanback.app.ErrorFragment {
|
||||
private static final String TAG = "ErrorFragment";
|
||||
private static final boolean TRANSLUCENT = true;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
Log.d(TAG, "onCreate");
|
||||
super.onCreate(savedInstanceState);
|
||||
setTitle(getResources().getString(R.string.app_name));
|
||||
}
|
||||
|
||||
void setErrorContent() {
|
||||
setImageDrawable(getResources().getDrawable(R.drawable.lb_ic_sad_cloud));
|
||||
setMessage(getResources().getString(R.string.error_fragment_message));
|
||||
setDefaultBackground(TRANSLUCENT);
|
||||
|
||||
setButtonText(getResources().getString(R.string.dismiss_error));
|
||||
setButtonClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View arg0) {
|
||||
getFragmentManager().beginTransaction().remove(ErrorFragment.this).commit();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.tvleanback.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.example.android.tvleanback.R;
|
||||
|
||||
/*
|
||||
* MainActivity class that loads MainFragment
|
||||
*/
|
||||
public class MainActivity extends Activity {
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.main);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSearchRequested() {
|
||||
startActivity(new Intent(this, SearchActivity.class));
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,300 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.tvleanback.ui;
|
||||
|
||||
import android.app.LoaderManager;
|
||||
import android.content.Intent;
|
||||
import android.content.Loader;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v17.leanback.app.BackgroundManager;
|
||||
import android.support.v17.leanback.app.BrowseFragment;
|
||||
import android.support.v17.leanback.widget.ArrayObjectAdapter;
|
||||
import android.support.v17.leanback.widget.HeaderItem;
|
||||
import android.support.v17.leanback.widget.ImageCardView;
|
||||
import android.support.v17.leanback.widget.ListRow;
|
||||
import android.support.v17.leanback.widget.ListRowPresenter;
|
||||
import android.support.v17.leanback.widget.OnItemViewClickedListener;
|
||||
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
|
||||
import android.support.v17.leanback.widget.Presenter;
|
||||
import android.support.v17.leanback.widget.Row;
|
||||
import android.support.v17.leanback.widget.RowPresenter;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
|
||||
import com.bumptech.glide.request.animation.GlideAnimation;
|
||||
import com.bumptech.glide.request.target.SimpleTarget;
|
||||
|
||||
import com.example.android.tvleanback.R;
|
||||
import com.example.android.tvleanback.data.VideoItemLoader;
|
||||
import com.example.android.tvleanback.data.VideoProvider;
|
||||
import com.example.android.tvleanback.model.Movie;
|
||||
import com.example.android.tvleanback.presenter.CardPresenter;
|
||||
import com.example.android.tvleanback.presenter.GridItemPresenter;
|
||||
import com.example.android.tvleanback.recommendation.UpdateRecommendationsService;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/*
|
||||
* Main class to show BrowseFragment with header and rows of videos
|
||||
*/
|
||||
public class MainFragment extends BrowseFragment implements
|
||||
LoaderManager.LoaderCallbacks<HashMap<String, List<Movie>>> {
|
||||
private static final String TAG = "MainFragment";
|
||||
|
||||
private static int BACKGROUND_UPDATE_DELAY = 300;
|
||||
private static String mVideosUrl;
|
||||
private final Handler mHandler = new Handler();
|
||||
private ArrayObjectAdapter mRowsAdapter;
|
||||
private Drawable mDefaultBackground;
|
||||
private DisplayMetrics mMetrics;
|
||||
private Timer mBackgroundTimer;
|
||||
private URI mBackgroundURI;
|
||||
private BackgroundManager mBackgroundManager;
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
Log.d(TAG, "onCreate");
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
loadVideoData();
|
||||
|
||||
prepareBackgroundManager();
|
||||
setupUIElements();
|
||||
setupEventListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (null != mBackgroundTimer) {
|
||||
Log.d(TAG, "onDestroy: " + mBackgroundTimer.toString());
|
||||
mBackgroundTimer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareBackgroundManager() {
|
||||
mBackgroundManager = BackgroundManager.getInstance(getActivity());
|
||||
mBackgroundManager.attach(getActivity().getWindow());
|
||||
mDefaultBackground = getResources().getDrawable(R.drawable.default_background);
|
||||
mMetrics = new DisplayMetrics();
|
||||
getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
|
||||
}
|
||||
|
||||
private void setupUIElements() {
|
||||
setBadgeDrawable(getActivity().getResources().getDrawable(R.drawable.videos_by_google_banner));
|
||||
setTitle(getString(R.string.browse_title)); // Badge, when set, takes precedent over title
|
||||
setHeadersState(HEADERS_ENABLED);
|
||||
setHeadersTransitionOnBackEnabled(true);
|
||||
// set fastLane (or headers) background color
|
||||
setBrandColor(getResources().getColor(R.color.fastlane_background));
|
||||
// set search icon color
|
||||
setSearchAffordanceColor(getResources().getColor(R.color.search_opaque));
|
||||
}
|
||||
|
||||
private void loadVideoData() {
|
||||
VideoProvider.setContext(getActivity());
|
||||
mVideosUrl = getActivity().getResources().getString(R.string.catalog_url);
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
private void setupEventListeners() {
|
||||
setOnSearchClickedListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent intent = new Intent(getActivity(), SearchActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
setOnItemViewClickedListener(new ItemViewClickedListener());
|
||||
setOnItemViewSelectedListener(new ItemViewSelectedListener());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader(int,
|
||||
* android.os.Bundle)
|
||||
*/
|
||||
@Override
|
||||
public Loader<HashMap<String, List<Movie>>> onCreateLoader(int arg0, Bundle arg1) {
|
||||
Log.d(TAG, "VideoItemLoader created ");
|
||||
return new VideoItemLoader(getActivity(), mVideosUrl);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see android.support.v4.app.LoaderManager.LoaderCallbacks#onLoadFinished(android
|
||||
* .support.v4.content.Loader, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public void onLoadFinished(Loader<HashMap<String, List<Movie>>> arg0,
|
||||
HashMap<String, List<Movie>> data) {
|
||||
|
||||
mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
|
||||
CardPresenter cardPresenter = new CardPresenter();
|
||||
|
||||
int i = 0;
|
||||
|
||||
for (Map.Entry<String, List<Movie>> entry : data.entrySet()) {
|
||||
ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
|
||||
List<Movie> list = entry.getValue();
|
||||
|
||||
for (int j = 0; j < list.size(); j++) {
|
||||
listRowAdapter.add(list.get(j));
|
||||
}
|
||||
HeaderItem header = new HeaderItem(i, entry.getKey());
|
||||
i++;
|
||||
mRowsAdapter.add(new ListRow(header, listRowAdapter));
|
||||
}
|
||||
|
||||
HeaderItem gridHeader = new HeaderItem(i, getString(R.string.more_samples));
|
||||
|
||||
GridItemPresenter gridPresenter = new GridItemPresenter(this);
|
||||
ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(gridPresenter);
|
||||
gridRowAdapter.add(getString(R.string.grid_view));
|
||||
gridRowAdapter.add(getString(R.string.error_fragment));
|
||||
gridRowAdapter.add(getString(R.string.personal_settings));
|
||||
mRowsAdapter.add(new ListRow(gridHeader, gridRowAdapter));
|
||||
|
||||
setAdapter(mRowsAdapter);
|
||||
|
||||
updateRecommendations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<HashMap<String, List<Movie>>> arg0) {
|
||||
mRowsAdapter.clear();
|
||||
}
|
||||
|
||||
protected void setDefaultBackground(Drawable background) {
|
||||
mDefaultBackground = background;
|
||||
}
|
||||
|
||||
protected void setDefaultBackground(int resourceId) {
|
||||
mDefaultBackground = getResources().getDrawable(resourceId);
|
||||
}
|
||||
|
||||
protected void updateBackground(String uri) {
|
||||
int width = mMetrics.widthPixels;
|
||||
int height = mMetrics.heightPixels;
|
||||
Glide.with(getActivity())
|
||||
.load(uri)
|
||||
.centerCrop()
|
||||
.error(mDefaultBackground)
|
||||
.into(new SimpleTarget<GlideDrawable>(width, height) {
|
||||
@Override
|
||||
public void onResourceReady(GlideDrawable resource,
|
||||
GlideAnimation<? super GlideDrawable>
|
||||
glideAnimation) {
|
||||
mBackgroundManager.setDrawable(resource);
|
||||
}
|
||||
});
|
||||
mBackgroundTimer.cancel();
|
||||
}
|
||||
|
||||
protected void updateBackground(Drawable drawable) {
|
||||
BackgroundManager.getInstance(getActivity()).setDrawable(drawable);
|
||||
}
|
||||
|
||||
protected void clearBackground() {
|
||||
BackgroundManager.getInstance(getActivity()).setDrawable(mDefaultBackground);
|
||||
}
|
||||
|
||||
private void startBackgroundTimer() {
|
||||
if (null != mBackgroundTimer) {
|
||||
mBackgroundTimer.cancel();
|
||||
}
|
||||
mBackgroundTimer = new Timer();
|
||||
mBackgroundTimer.schedule(new UpdateBackgroundTask(), BACKGROUND_UPDATE_DELAY);
|
||||
}
|
||||
|
||||
private void updateRecommendations() {
|
||||
Intent recommendationIntent = new Intent(getActivity(), UpdateRecommendationsService.class);
|
||||
getActivity().startService(recommendationIntent);
|
||||
}
|
||||
|
||||
private class UpdateBackgroundTask extends TimerTask {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mBackgroundURI != null) {
|
||||
updateBackground(mBackgroundURI.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private final class ItemViewClickedListener implements OnItemViewClickedListener {
|
||||
@Override
|
||||
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
|
||||
RowPresenter.ViewHolder rowViewHolder, Row row) {
|
||||
|
||||
if (item instanceof Movie) {
|
||||
Movie movie = (Movie) item;
|
||||
Log.d(TAG, "Movie: " + movie.toString());
|
||||
Intent intent = new Intent(getActivity(), MovieDetailsActivity.class);
|
||||
intent.putExtra(MovieDetailsActivity.MOVIE, movie);
|
||||
|
||||
Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||
getActivity(),
|
||||
((ImageCardView) itemViewHolder.view).getMainImageView(),
|
||||
MovieDetailsActivity.SHARED_ELEMENT_NAME).toBundle();
|
||||
getActivity().startActivity(intent, bundle);
|
||||
} else if (item instanceof String) {
|
||||
if (((String) item).indexOf(getString(R.string.grid_view)) >= 0) {
|
||||
Intent intent = new Intent(getActivity(), VerticalGridActivity.class);
|
||||
startActivity(intent);
|
||||
} else if (((String) item).indexOf(getString(R.string.error_fragment)) >= 0) {
|
||||
Intent intent = new Intent(getActivity(), BrowseErrorActivity.class);
|
||||
startActivity(intent);
|
||||
} else {
|
||||
Toast.makeText(getActivity(), ((String) item), Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final class ItemViewSelectedListener implements OnItemViewSelectedListener {
|
||||
@Override
|
||||
public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
|
||||
RowPresenter.ViewHolder rowViewHolder, Row row) {
|
||||
if (item instanceof Movie) {
|
||||
mBackgroundURI = ((Movie) item).getBackgroundImageURI();
|
||||
startBackgroundTimer();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.tvleanback.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.example.android.tvleanback.R;
|
||||
|
||||
/*
|
||||
* Details activity class that loads LeanbackDetailsFragment class
|
||||
*/
|
||||
public class MovieDetailsActivity extends Activity {
|
||||
public static final String SHARED_ELEMENT_NAME = "hero";
|
||||
public static final String MOVIE = "Movie";
|
||||
public static final String NOTIFICATION_ID = "NotificationId";
|
||||
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.details);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSearchRequested() {
|
||||
startActivity(new Intent(this, SearchActivity.class));
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.tvleanback.ui;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v17.leanback.app.BackgroundManager;
|
||||
import android.support.v17.leanback.widget.Action;
|
||||
import android.support.v17.leanback.widget.ArrayObjectAdapter;
|
||||
import android.support.v17.leanback.widget.ClassPresenterSelector;
|
||||
import android.support.v17.leanback.widget.DetailsOverviewRow;
|
||||
import android.support.v17.leanback.widget.DetailsOverviewRowPresenter;
|
||||
import android.support.v17.leanback.widget.HeaderItem;
|
||||
import android.support.v17.leanback.widget.ImageCardView;
|
||||
import android.support.v17.leanback.widget.ListRow;
|
||||
import android.support.v17.leanback.widget.ListRowPresenter;
|
||||
import android.support.v17.leanback.widget.OnActionClickedListener;
|
||||
import android.support.v17.leanback.widget.OnItemViewClickedListener;
|
||||
import android.support.v17.leanback.widget.Presenter;
|
||||
import android.support.v17.leanback.widget.Row;
|
||||
import android.support.v17.leanback.widget.RowPresenter;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
|
||||
import com.bumptech.glide.request.animation.GlideAnimation;
|
||||
import com.bumptech.glide.request.target.SimpleTarget;
|
||||
|
||||
import com.example.android.tvleanback.R;
|
||||
import com.example.android.tvleanback.Utils;
|
||||
import com.example.android.tvleanback.data.VideoProvider;
|
||||
import com.example.android.tvleanback.model.Movie;
|
||||
import com.example.android.tvleanback.presenter.CardPresenter;
|
||||
import com.example.android.tvleanback.presenter.DetailsDescriptionPresenter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* LeanbackDetailsFragment extends DetailsFragment, a Wrapper fragment for leanback details screens.
|
||||
* It shows a detailed view of video and its meta plus related videos.
|
||||
*/
|
||||
public class MovieDetailsFragment extends android.support.v17.leanback.app.DetailsFragment {
|
||||
private static final String TAG = "DetailsFragment";
|
||||
|
||||
private static final int ACTION_WATCH_TRAILER = 1;
|
||||
private static final int ACTION_RENT = 2;
|
||||
private static final int ACTION_BUY = 3;
|
||||
|
||||
private static final int DETAIL_THUMB_WIDTH = 274;
|
||||
private static final int DETAIL_THUMB_HEIGHT = 274;
|
||||
|
||||
private static final int NO_NOTIFICATION = -1;
|
||||
|
||||
private Movie mSelectedMovie;
|
||||
|
||||
private ArrayObjectAdapter mAdapter;
|
||||
private ClassPresenterSelector mPresenterSelector;
|
||||
|
||||
private BackgroundManager mBackgroundManager;
|
||||
private Drawable mDefaultBackground;
|
||||
private DisplayMetrics mMetrics;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
Log.d(TAG, "onCreate DetailsFragment");
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
prepareBackgroundManager();
|
||||
|
||||
mSelectedMovie = (Movie) getActivity().getIntent()
|
||||
.getParcelableExtra(MovieDetailsActivity.MOVIE);
|
||||
if (mSelectedMovie != null || checkGlobalSearchIntent()) {
|
||||
removeNotification(getActivity().getIntent()
|
||||
.getIntExtra(MovieDetailsActivity.NOTIFICATION_ID, NO_NOTIFICATION));
|
||||
setupAdapter();
|
||||
setupDetailsOverviewRow();
|
||||
setupDetailsOverviewRowPresenter();
|
||||
setupMovieListRow();
|
||||
setupMovieListRowPresenter();
|
||||
updateBackground(mSelectedMovie.getBackgroundImageUrl());
|
||||
setOnItemViewClickedListener(new ItemViewClickedListener());
|
||||
} else {
|
||||
Intent intent = new Intent(getActivity(), MainActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeNotification(int notificationId) {
|
||||
if (notificationId != NO_NOTIFICATION) {
|
||||
NotificationManager notificationManager = (NotificationManager) getActivity()
|
||||
.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancel(notificationId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if there is a global search intent
|
||||
*/
|
||||
private boolean checkGlobalSearchIntent() {
|
||||
Intent intent = getActivity().getIntent();
|
||||
String intentAction = intent.getAction();
|
||||
String globalSearch = getString(R.string.global_search);
|
||||
if (globalSearch.equalsIgnoreCase(intentAction)) {
|
||||
Uri intentData = intent.getData();
|
||||
Log.d(TAG, "action: " + intentAction + " intentData:" + intentData);
|
||||
int selectedIndex = Integer.parseInt(intentData.getLastPathSegment());
|
||||
HashMap<String, List<Movie>> movies = VideoProvider.getMovieList();
|
||||
int movieTally = 0;
|
||||
if (movies == null) {
|
||||
return false;
|
||||
}
|
||||
for (Map.Entry<String, List<Movie>> entry : movies.entrySet()) {
|
||||
List<Movie> list = entry.getValue();
|
||||
for (Movie movie : list) {
|
||||
movieTally++;
|
||||
if (selectedIndex == movieTally) {
|
||||
mSelectedMovie = movie;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void prepareBackgroundManager() {
|
||||
mBackgroundManager = BackgroundManager.getInstance(getActivity());
|
||||
mBackgroundManager.attach(getActivity().getWindow());
|
||||
mDefaultBackground = getResources().getDrawable(R.drawable.default_background);
|
||||
mMetrics = new DisplayMetrics();
|
||||
getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
|
||||
}
|
||||
|
||||
protected void updateBackground(String uri) {
|
||||
Glide.with(getActivity())
|
||||
.load(uri)
|
||||
.centerCrop()
|
||||
.error(mDefaultBackground)
|
||||
.into(new SimpleTarget<GlideDrawable>(mMetrics.widthPixels, mMetrics.heightPixels) {
|
||||
@Override
|
||||
public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
|
||||
mBackgroundManager.setDrawable(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupAdapter() {
|
||||
mPresenterSelector = new ClassPresenterSelector();
|
||||
mAdapter = new ArrayObjectAdapter(mPresenterSelector);
|
||||
setAdapter(mAdapter);
|
||||
}
|
||||
|
||||
private void setupDetailsOverviewRow() {
|
||||
Log.d(TAG, "doInBackground: " + mSelectedMovie.toString());
|
||||
final DetailsOverviewRow row = new DetailsOverviewRow(mSelectedMovie);
|
||||
row.setImageDrawable(getResources().getDrawable(R.drawable.default_background));
|
||||
int width = Utils.convertDpToPixel(getActivity()
|
||||
.getApplicationContext(), DETAIL_THUMB_WIDTH);
|
||||
int height = Utils.convertDpToPixel(getActivity()
|
||||
.getApplicationContext(), DETAIL_THUMB_HEIGHT);
|
||||
Glide.with(getActivity())
|
||||
.load(mSelectedMovie.getCardImageUrl())
|
||||
.centerCrop()
|
||||
.error(R.drawable.default_background)
|
||||
.into(new SimpleTarget<GlideDrawable>(width, height) {
|
||||
@Override
|
||||
public void onResourceReady(GlideDrawable resource,
|
||||
GlideAnimation<? super GlideDrawable>
|
||||
glideAnimation) {
|
||||
Log.d(TAG, "details overview card image url ready: " + resource);
|
||||
row.setImageDrawable(resource);
|
||||
mAdapter.notifyArrayItemRangeChanged(0, mAdapter.size());
|
||||
}
|
||||
});
|
||||
|
||||
row.addAction(new Action(ACTION_WATCH_TRAILER, getResources().getString(
|
||||
R.string.watch_trailer_1), getResources().getString(R.string.watch_trailer_2)));
|
||||
row.addAction(new Action(ACTION_RENT, getResources().getString(R.string.rent_1),
|
||||
getResources().getString(R.string.rent_2)));
|
||||
row.addAction(new Action(ACTION_BUY, getResources().getString(R.string.buy_1),
|
||||
getResources().getString(R.string.buy_2)));
|
||||
|
||||
mAdapter.add(row);
|
||||
}
|
||||
|
||||
private void setupDetailsOverviewRowPresenter() {
|
||||
// Set detail background and style.
|
||||
DetailsOverviewRowPresenter detailsPresenter =
|
||||
new DetailsOverviewRowPresenter(new DetailsDescriptionPresenter());
|
||||
detailsPresenter.setBackgroundColor(getResources().getColor(R.color.selected_background));
|
||||
detailsPresenter.setStyleLarge(true);
|
||||
|
||||
// Hook up transition element.
|
||||
detailsPresenter.setSharedElementEnterTransition(getActivity(),
|
||||
MovieDetailsActivity.SHARED_ELEMENT_NAME);
|
||||
|
||||
detailsPresenter.setOnActionClickedListener(new OnActionClickedListener() {
|
||||
@Override
|
||||
public void onActionClicked(Action action) {
|
||||
if (action.getId() == ACTION_WATCH_TRAILER) {
|
||||
Intent intent = new Intent(getActivity(), PlaybackOverlayActivity.class);
|
||||
intent.putExtra(MovieDetailsActivity.MOVIE, mSelectedMovie);
|
||||
startActivity(intent);
|
||||
} else {
|
||||
Toast.makeText(getActivity(), action.toString(), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
mPresenterSelector.addClassPresenter(DetailsOverviewRow.class, detailsPresenter);
|
||||
}
|
||||
|
||||
private void setupMovieListRow() {
|
||||
String subcategories[] = {getString(R.string.related_movies)};
|
||||
HashMap<String, List<Movie>> movies = VideoProvider.getMovieList();
|
||||
|
||||
ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
|
||||
for (Map.Entry<String, List<Movie>> entry : movies.entrySet()) {
|
||||
if (mSelectedMovie.getCategory().indexOf(entry.getKey()) >= 0) {
|
||||
List<Movie> list = entry.getValue();
|
||||
for (int j = 0; j < list.size(); j++) {
|
||||
listRowAdapter.add(list.get(j));
|
||||
}
|
||||
}
|
||||
}
|
||||
HeaderItem header = new HeaderItem(0, subcategories[0]);
|
||||
mAdapter.add(new ListRow(header, listRowAdapter));
|
||||
}
|
||||
|
||||
private void setupMovieListRowPresenter() {
|
||||
mPresenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter());
|
||||
}
|
||||
|
||||
private final class ItemViewClickedListener implements OnItemViewClickedListener {
|
||||
@Override
|
||||
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
|
||||
RowPresenter.ViewHolder rowViewHolder, Row row) {
|
||||
|
||||
if (item instanceof Movie) {
|
||||
Movie movie = (Movie) item;
|
||||
Log.d(TAG, "Item: " + item.toString());
|
||||
Intent intent = new Intent(getActivity(), MovieDetailsActivity.class);
|
||||
intent.putExtra(MovieDetailsActivity.MOVIE, movie);
|
||||
|
||||
Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||
getActivity(),
|
||||
((ImageCardView) itemViewHolder.view).getMainImageView(),
|
||||
MovieDetailsActivity.SHARED_ELEMENT_NAME).toBundle();
|
||||
getActivity().startActivity(intent, bundle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,282 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.tvleanback.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.MediaMetadata;
|
||||
import android.media.MediaPlayer;
|
||||
import android.media.session.MediaSession;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.VideoView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.animation.GlideAnimation;
|
||||
import com.bumptech.glide.request.target.SimpleTarget;
|
||||
import com.example.android.tvleanback.R;
|
||||
import com.example.android.tvleanback.model.Movie;
|
||||
|
||||
/**
|
||||
* PlaybackOverlayActivity for video playback that loads PlaybackOverlayFragment
|
||||
*/
|
||||
public class PlaybackOverlayActivity extends Activity implements
|
||||
PlaybackOverlayFragment.OnPlayPauseClickedListener {
|
||||
private static final String TAG = "PlaybackOverlayActivity";
|
||||
|
||||
private static final double MEDIA_HEIGHT = 0.95;
|
||||
private static final double MEDIA_WIDTH = 0.95;
|
||||
private static final double MEDIA_TOP_MARGIN = 0.025;
|
||||
private static final double MEDIA_RIGHT_MARGIN = 0.025;
|
||||
private static final double MEDIA_BOTTOM_MARGIN = 0.025;
|
||||
private static final double MEDIA_LEFT_MARGIN = 0.025;
|
||||
private VideoView mVideoView;
|
||||
private LeanbackPlaybackState mPlaybackState = LeanbackPlaybackState.IDLE;
|
||||
private MediaSession mSession;
|
||||
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.playback_controls);
|
||||
loadViews();
|
||||
//Example for handling resizing view for overscan
|
||||
//overScan();
|
||||
|
||||
mSession = new MediaSession (this, "LeanbackSampleApp");
|
||||
mSession.setCallback(new MediaSessionCallback());
|
||||
mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
|
||||
MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
|
||||
|
||||
mSession.setActive(true);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
mVideoView.suspend();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of OnPlayPauseClickedListener
|
||||
*/
|
||||
public void onFragmentPlayPause(Movie movie, int position, Boolean playPause) {
|
||||
mVideoView.setVideoPath(movie.getVideoUrl());
|
||||
|
||||
if (position == 0 || mPlaybackState == LeanbackPlaybackState.IDLE) {
|
||||
setupCallbacks();
|
||||
mPlaybackState = LeanbackPlaybackState.IDLE;
|
||||
}
|
||||
|
||||
if (playPause && mPlaybackState != LeanbackPlaybackState.PLAYING) {
|
||||
mPlaybackState = LeanbackPlaybackState.PLAYING;
|
||||
if (position > 0) {
|
||||
mVideoView.seekTo(position);
|
||||
mVideoView.start();
|
||||
}
|
||||
} else {
|
||||
mPlaybackState = LeanbackPlaybackState.PAUSED;
|
||||
mVideoView.pause();
|
||||
}
|
||||
updatePlaybackState(position);
|
||||
updateMetadata(movie);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of OnPlayPauseClickedListener
|
||||
*/
|
||||
public void onFragmentFfwRwd(Movie movie, int position) {
|
||||
mVideoView.setVideoPath(movie.getVideoUrl());
|
||||
|
||||
Log.d(TAG, "seek current time: " + position);
|
||||
if (mPlaybackState == LeanbackPlaybackState.PLAYING) {
|
||||
if (position > 0) {
|
||||
mVideoView.seekTo(position);
|
||||
mVideoView.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePlaybackState(int position) {
|
||||
PlaybackState.Builder stateBuilder = new PlaybackState.Builder()
|
||||
.setActions(getAvailableActions());
|
||||
int state = PlaybackState.STATE_PLAYING;
|
||||
if (mPlaybackState == LeanbackPlaybackState.PAUSED) {
|
||||
state = PlaybackState.STATE_PAUSED;
|
||||
}
|
||||
stateBuilder.setState(state, position, 1.0f);
|
||||
mSession.setPlaybackState(stateBuilder.build());
|
||||
}
|
||||
|
||||
private long getAvailableActions() {
|
||||
long actions = PlaybackState.ACTION_PLAY |
|
||||
PlaybackState.ACTION_PLAY_FROM_MEDIA_ID |
|
||||
PlaybackState.ACTION_PLAY_FROM_SEARCH;
|
||||
|
||||
if (mPlaybackState == LeanbackPlaybackState.PLAYING) {
|
||||
actions |= PlaybackState.ACTION_PAUSE;
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
private void updateMetadata(final Movie movie) {
|
||||
final MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder();
|
||||
|
||||
String title = movie.getTitle().replace("_", " -");
|
||||
|
||||
metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, title);
|
||||
metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE,
|
||||
movie.getDescription());
|
||||
metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI,
|
||||
movie.getCardImageUrl());
|
||||
|
||||
// And at minimum the title and artist for legacy support
|
||||
metadataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE, title);
|
||||
metadataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST, movie.getStudio());
|
||||
|
||||
Glide.with(this)
|
||||
.load(Uri.parse(movie.getCardImageUrl()))
|
||||
.asBitmap()
|
||||
.into(new SimpleTarget<Bitmap>(500, 500) {
|
||||
@Override
|
||||
public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
|
||||
metadataBuilder.putBitmap(MediaMetadata.METADATA_KEY_ART, bitmap);
|
||||
mSession.setMetadata(metadataBuilder.build());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadViews() {
|
||||
mVideoView = (VideoView) findViewById(R.id.videoView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Example for handling resizing content for overscan. Typically you won't need to resize which
|
||||
* is why overScan(); is commented out.
|
||||
*/
|
||||
private void overScan() {
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
getWindowManager().getDefaultDisplay().getMetrics(metrics);
|
||||
int w = (int) (metrics.widthPixels * MEDIA_WIDTH);
|
||||
int h = (int) (metrics.heightPixels * MEDIA_HEIGHT);
|
||||
int marginLeft = (int) (metrics.widthPixels * MEDIA_LEFT_MARGIN);
|
||||
int marginTop = (int) (metrics.heightPixels * MEDIA_TOP_MARGIN);
|
||||
int marginRight = (int) (metrics.widthPixels * MEDIA_RIGHT_MARGIN);
|
||||
int marginBottom = (int) (metrics.heightPixels * MEDIA_BOTTOM_MARGIN);
|
||||
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(w, h);
|
||||
lp.setMargins(marginLeft, marginTop, marginRight, marginBottom);
|
||||
mVideoView.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
private void setupCallbacks() {
|
||||
|
||||
mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
|
||||
|
||||
@Override
|
||||
public boolean onError(MediaPlayer mp, int what, int extra) {
|
||||
String msg = "";
|
||||
if (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) {
|
||||
msg = getString(R.string.video_error_media_load_timeout);
|
||||
} else if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
|
||||
msg = getString(R.string.video_error_server_inaccessible);
|
||||
} else {
|
||||
msg = getString(R.string.video_error_unknown_error);
|
||||
}
|
||||
mVideoView.stopPlayback();
|
||||
mPlaybackState = LeanbackPlaybackState.IDLE;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
|
||||
@Override
|
||||
public void onPrepared(MediaPlayer mp) {
|
||||
if (mPlaybackState == LeanbackPlaybackState.PLAYING) {
|
||||
mVideoView.start();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
|
||||
@Override
|
||||
public void onCompletion(MediaPlayer mp) {
|
||||
mPlaybackState = LeanbackPlaybackState.IDLE;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (mVideoView.isPlaying()) {
|
||||
if (!requestVisibleBehind(true)) {
|
||||
// Try to play behind launcher, but if it fails, stop playback.
|
||||
stopPlayback();
|
||||
}
|
||||
} else {
|
||||
requestVisibleBehind(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
mSession.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVisibleBehindCanceled() {
|
||||
super.onVisibleBehindCanceled();
|
||||
stopPlayback();
|
||||
}
|
||||
|
||||
private void stopPlayback() {
|
||||
if (mVideoView != null) {
|
||||
mVideoView.stopPlayback();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSearchRequested() {
|
||||
startActivity(new Intent(this, SearchActivity.class));
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* List of various states that we can be in
|
||||
*/
|
||||
public static enum LeanbackPlaybackState {
|
||||
PLAYING, PAUSED, BUFFERING, IDLE;
|
||||
}
|
||||
|
||||
private class MediaSessionCallback extends MediaSession.Callback {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,502 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.tvleanback.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.MediaMetadataRetriever;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
|
||||
import android.support.v17.leanback.widget.Action;
|
||||
import android.support.v17.leanback.widget.ArrayObjectAdapter;
|
||||
import android.support.v17.leanback.widget.ClassPresenterSelector;
|
||||
import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
|
||||
import android.support.v17.leanback.widget.HeaderItem;
|
||||
import android.support.v17.leanback.widget.ImageCardView;
|
||||
import android.support.v17.leanback.widget.ListRow;
|
||||
import android.support.v17.leanback.widget.ListRowPresenter;
|
||||
import android.support.v17.leanback.widget.OnActionClickedListener;
|
||||
import android.support.v17.leanback.widget.OnItemViewClickedListener;
|
||||
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
|
||||
import android.support.v17.leanback.widget.PlaybackControlsRow;
|
||||
import android.support.v17.leanback.widget.PlaybackControlsRow.FastForwardAction;
|
||||
import android.support.v17.leanback.widget.PlaybackControlsRow.PlayPauseAction;
|
||||
import android.support.v17.leanback.widget.PlaybackControlsRow.RepeatAction;
|
||||
import android.support.v17.leanback.widget.PlaybackControlsRow.RewindAction;
|
||||
import android.support.v17.leanback.widget.PlaybackControlsRow.ShuffleAction;
|
||||
import android.support.v17.leanback.widget.PlaybackControlsRow.SkipNextAction;
|
||||
import android.support.v17.leanback.widget.PlaybackControlsRow.SkipPreviousAction;
|
||||
import android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsDownAction;
|
||||
import android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsUpAction;
|
||||
import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
|
||||
import android.support.v17.leanback.widget.Presenter;
|
||||
import android.support.v17.leanback.widget.Row;
|
||||
import android.support.v17.leanback.widget.RowPresenter;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
|
||||
import com.bumptech.glide.request.animation.GlideAnimation;
|
||||
import com.bumptech.glide.request.target.SimpleTarget;
|
||||
import com.example.android.tvleanback.R;
|
||||
import com.example.android.tvleanback.data.VideoProvider;
|
||||
import com.example.android.tvleanback.model.Movie;
|
||||
import com.example.android.tvleanback.presenter.CardPresenter;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/*
|
||||
* Class for video playback with media control
|
||||
*/
|
||||
public class PlaybackOverlayFragment extends android.support.v17.leanback.app.PlaybackOverlayFragment {
|
||||
private static final String TAG = "PlaybackOverlayFragment";
|
||||
private static final boolean SHOW_DETAIL = true;
|
||||
private static final boolean HIDE_MORE_ACTIONS = false;
|
||||
private static final int PRIMARY_CONTROLS = 5;
|
||||
private static final boolean SHOW_IMAGE = PRIMARY_CONTROLS <= 5;
|
||||
private static final int BACKGROUND_TYPE = PlaybackOverlayFragment.BG_LIGHT;
|
||||
private static final int CARD_WIDTH = 150;
|
||||
private static final int CARD_HEIGHT = 240;
|
||||
private static final int DEFAULT_UPDATE_PERIOD = 1000;
|
||||
private static final int UPDATE_PERIOD = 16;
|
||||
private static final int SIMULATED_BUFFERED_TIME = 10000;
|
||||
private static final int CLICK_TRACKING_DELAY = 1000;
|
||||
private static final int INITIAL_SPEED = 10000;
|
||||
|
||||
private static Context sContext;
|
||||
private final Handler mClickTrackingHandler = new Handler();
|
||||
OnPlayPauseClickedListener mCallback;
|
||||
private ArrayObjectAdapter mRowsAdapter;
|
||||
private ArrayObjectAdapter mPrimaryActionsAdapter;
|
||||
private ArrayObjectAdapter mSecondaryActionsAdapter;
|
||||
private PlayPauseAction mPlayPauseAction;
|
||||
private RepeatAction mRepeatAction;
|
||||
private ThumbsUpAction mThumbsUpAction;
|
||||
private ThumbsDownAction mThumbsDownAction;
|
||||
private ShuffleAction mShuffleAction;
|
||||
private FastForwardAction mFastForwardAction;
|
||||
private RewindAction mRewindAction;
|
||||
private SkipNextAction mSkipNextAction;
|
||||
private SkipPreviousAction mSkipPreviousAction;
|
||||
private PlaybackControlsRow mPlaybackControlsRow;
|
||||
private ArrayList<Movie> mItems = new ArrayList<Movie>();
|
||||
private int mCurrentItem;
|
||||
private long mDuration;
|
||||
private Handler mHandler;
|
||||
private Runnable mRunnable;
|
||||
private Movie mSelectedMovie;
|
||||
private int mFfwRwdSpeed = INITIAL_SPEED;
|
||||
private Timer mClickTrackingTimer;
|
||||
private int mClickCount;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
Log.i(TAG, "onCreate");
|
||||
super.onCreate(savedInstanceState);
|
||||
sContext = getActivity();
|
||||
|
||||
mItems = new ArrayList<Movie>();
|
||||
mSelectedMovie = (Movie) getActivity()
|
||||
.getIntent().getParcelableExtra(MovieDetailsActivity.MOVIE);
|
||||
|
||||
HashMap<String, List<Movie>> movies = VideoProvider.getMovieList();
|
||||
|
||||
if(movies != null) {
|
||||
for (Map.Entry<String, List<Movie>> entry : movies.entrySet()) {
|
||||
if (mSelectedMovie.getCategory().contains(entry.getKey())) {
|
||||
List<Movie> list = entry.getValue();
|
||||
if(list != null && !list.isEmpty()) {
|
||||
for (int j = 0; j < list.size(); j++) {
|
||||
mItems.add(list.get(j));
|
||||
if (mSelectedMovie.getTitle().contentEquals(list.get(j).getTitle())) {
|
||||
mCurrentItem = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mHandler = new Handler();
|
||||
|
||||
setBackgroundType(BACKGROUND_TYPE);
|
||||
setFadingEnabled(false);
|
||||
|
||||
setupRows();
|
||||
|
||||
setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
|
||||
RowPresenter.ViewHolder rowViewHolder, Row row) {
|
||||
Log.i(TAG, "onItemSelected: " + item + " row " + row);
|
||||
}
|
||||
});
|
||||
setOnItemViewClickedListener(new ItemViewClickedListener());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
// This makes sure that the container activity has implemented
|
||||
// the callback interface. If not, it throws an exception
|
||||
try {
|
||||
mCallback = (OnPlayPauseClickedListener) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement OnPlayPauseClickedListener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume(){
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
private void setupRows() {
|
||||
|
||||
ClassPresenterSelector ps = new ClassPresenterSelector();
|
||||
|
||||
PlaybackControlsRowPresenter playbackControlsRowPresenter;
|
||||
if (SHOW_DETAIL) {
|
||||
playbackControlsRowPresenter = new PlaybackControlsRowPresenter(
|
||||
new DescriptionPresenter());
|
||||
} else {
|
||||
playbackControlsRowPresenter = new PlaybackControlsRowPresenter();
|
||||
}
|
||||
playbackControlsRowPresenter.setOnActionClickedListener(new OnActionClickedListener() {
|
||||
public void onActionClicked(Action action) {
|
||||
if (action.getId() == mPlayPauseAction.getId()) {
|
||||
if (mPlayPauseAction.getIndex() == PlayPauseAction.PLAY) {
|
||||
startProgressAutomation();
|
||||
setFadingEnabled(true);
|
||||
mCallback.onFragmentPlayPause(mItems.get(mCurrentItem),
|
||||
mPlaybackControlsRow.getCurrentTime(), true);
|
||||
} else {
|
||||
stopProgressAutomation();
|
||||
setFadingEnabled(false);
|
||||
mCallback.onFragmentPlayPause(mItems.get(mCurrentItem),
|
||||
mPlaybackControlsRow.getCurrentTime(), false);
|
||||
}
|
||||
} else if (action.getId() == mSkipNextAction.getId()) {
|
||||
next();
|
||||
} else if (action.getId() == mSkipPreviousAction.getId()) {
|
||||
prev();
|
||||
} else if (action.getId() == mFastForwardAction.getId()) {
|
||||
fastForward();
|
||||
} else if (action.getId() == mRewindAction.getId()) {
|
||||
fastRewind();
|
||||
}
|
||||
if (action instanceof PlaybackControlsRow.MultiAction) {
|
||||
((PlaybackControlsRow.MultiAction) action).nextIndex();
|
||||
notifyChanged(action);
|
||||
}
|
||||
}
|
||||
});
|
||||
playbackControlsRowPresenter.setSecondaryActionsHidden(HIDE_MORE_ACTIONS);
|
||||
|
||||
ps.addClassPresenter(PlaybackControlsRow.class, playbackControlsRowPresenter);
|
||||
ps.addClassPresenter(ListRow.class, new ListRowPresenter());
|
||||
mRowsAdapter = new ArrayObjectAdapter(ps);
|
||||
|
||||
addPlaybackControlsRow();
|
||||
addOtherRows();
|
||||
|
||||
setAdapter(mRowsAdapter);
|
||||
}
|
||||
|
||||
private int getDuration() {
|
||||
Movie movie = mItems.get(mCurrentItem);
|
||||
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
mmr.setDataSource(movie.getVideoUrl(), new HashMap<String, String>());
|
||||
} else {
|
||||
mmr.setDataSource(movie.getVideoUrl());
|
||||
}
|
||||
String time = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
|
||||
mDuration = Long.parseLong(time);
|
||||
return (int) mDuration;
|
||||
}
|
||||
|
||||
private void addPlaybackControlsRow() {
|
||||
if (SHOW_DETAIL) {
|
||||
mPlaybackControlsRow = new PlaybackControlsRow(mSelectedMovie);
|
||||
} else {
|
||||
mPlaybackControlsRow = new PlaybackControlsRow();
|
||||
}
|
||||
mRowsAdapter.add(mPlaybackControlsRow);
|
||||
|
||||
updatePlaybackRow(mCurrentItem);
|
||||
|
||||
ControlButtonPresenterSelector presenterSelector = new ControlButtonPresenterSelector();
|
||||
mPrimaryActionsAdapter = new ArrayObjectAdapter(presenterSelector);
|
||||
mSecondaryActionsAdapter = new ArrayObjectAdapter(presenterSelector);
|
||||
mPlaybackControlsRow.setPrimaryActionsAdapter(mPrimaryActionsAdapter);
|
||||
mPlaybackControlsRow.setSecondaryActionsAdapter(mSecondaryActionsAdapter);
|
||||
|
||||
mPlayPauseAction = new PlayPauseAction(sContext);
|
||||
mRepeatAction = new RepeatAction(sContext);
|
||||
mThumbsUpAction = new ThumbsUpAction(sContext);
|
||||
mThumbsDownAction = new ThumbsDownAction(sContext);
|
||||
mShuffleAction = new ShuffleAction(sContext);
|
||||
mSkipNextAction = new PlaybackControlsRow.SkipNextAction(sContext);
|
||||
mSkipPreviousAction = new PlaybackControlsRow.SkipPreviousAction(sContext);
|
||||
mFastForwardAction = new PlaybackControlsRow.FastForwardAction(sContext);
|
||||
mRewindAction = new PlaybackControlsRow.RewindAction(sContext);
|
||||
|
||||
if (PRIMARY_CONTROLS > 5) {
|
||||
mPrimaryActionsAdapter.add(mThumbsUpAction);
|
||||
} else {
|
||||
mSecondaryActionsAdapter.add(mThumbsUpAction);
|
||||
}
|
||||
mPrimaryActionsAdapter.add(mSkipPreviousAction);
|
||||
if (PRIMARY_CONTROLS > 3) {
|
||||
mPrimaryActionsAdapter.add(new PlaybackControlsRow.RewindAction(sContext));
|
||||
}
|
||||
mPrimaryActionsAdapter.add(mPlayPauseAction);
|
||||
if (PRIMARY_CONTROLS > 3) {
|
||||
mPrimaryActionsAdapter.add(new PlaybackControlsRow.FastForwardAction(sContext));
|
||||
}
|
||||
mPrimaryActionsAdapter.add(mSkipNextAction);
|
||||
|
||||
mSecondaryActionsAdapter.add(mRepeatAction);
|
||||
mSecondaryActionsAdapter.add(mShuffleAction);
|
||||
if (PRIMARY_CONTROLS > 5) {
|
||||
mPrimaryActionsAdapter.add(mThumbsDownAction);
|
||||
} else {
|
||||
mSecondaryActionsAdapter.add(mThumbsDownAction);
|
||||
}
|
||||
mSecondaryActionsAdapter.add(new PlaybackControlsRow.HighQualityAction(sContext));
|
||||
mSecondaryActionsAdapter.add(new PlaybackControlsRow.ClosedCaptioningAction(sContext));
|
||||
}
|
||||
|
||||
private void notifyChanged(Action action) {
|
||||
ArrayObjectAdapter adapter = mPrimaryActionsAdapter;
|
||||
if (adapter.indexOf(action) >= 0) {
|
||||
adapter.notifyArrayItemRangeChanged(adapter.indexOf(action), 1);
|
||||
return;
|
||||
}
|
||||
adapter = mSecondaryActionsAdapter;
|
||||
if (adapter.indexOf(action) >= 0) {
|
||||
adapter.notifyArrayItemRangeChanged(adapter.indexOf(action), 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePlaybackRow(int index) {
|
||||
if (mPlaybackControlsRow.getItem() != null) {
|
||||
Movie item = (Movie) mPlaybackControlsRow.getItem();
|
||||
item.setTitle(mItems.get(mCurrentItem).getTitle());
|
||||
item.setStudio(mItems.get(mCurrentItem).getStudio());
|
||||
}
|
||||
if (SHOW_IMAGE) {
|
||||
updateVideoImage(mItems.get(mCurrentItem).getCardImageUrl());
|
||||
}
|
||||
mRowsAdapter.notifyArrayItemRangeChanged(0, 1);
|
||||
mPlaybackControlsRow.setTotalTime(getDuration());
|
||||
mPlaybackControlsRow.setCurrentTime(0);
|
||||
mPlaybackControlsRow.setBufferedProgress(0);
|
||||
}
|
||||
|
||||
private void addOtherRows() {
|
||||
ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
|
||||
for (Movie movie : mItems) {
|
||||
listRowAdapter.add(movie);
|
||||
}
|
||||
HeaderItem header = new HeaderItem(0, getString(R.string.related_movies));
|
||||
mRowsAdapter.add(new ListRow(header, listRowAdapter));
|
||||
|
||||
}
|
||||
|
||||
private int getUpdatePeriod() {
|
||||
if (getView() == null || mPlaybackControlsRow.getTotalTime() <= 0) {
|
||||
return DEFAULT_UPDATE_PERIOD;
|
||||
}
|
||||
return Math.max(UPDATE_PERIOD, mPlaybackControlsRow.getTotalTime() / getView().getWidth());
|
||||
}
|
||||
|
||||
private void startProgressAutomation() {
|
||||
mRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int updatePeriod = getUpdatePeriod();
|
||||
int currentTime = mPlaybackControlsRow.getCurrentTime() + updatePeriod;
|
||||
int totalTime = mPlaybackControlsRow.getTotalTime();
|
||||
mPlaybackControlsRow.setCurrentTime(currentTime);
|
||||
mPlaybackControlsRow.setBufferedProgress(currentTime + SIMULATED_BUFFERED_TIME);
|
||||
|
||||
if (totalTime > 0 && totalTime <= currentTime) {
|
||||
next();
|
||||
}
|
||||
mHandler.postDelayed(this, updatePeriod);
|
||||
}
|
||||
};
|
||||
mHandler.postDelayed(mRunnable, getUpdatePeriod());
|
||||
}
|
||||
|
||||
private void next() {
|
||||
if (++mCurrentItem >= mItems.size()) {
|
||||
mCurrentItem = 0;
|
||||
}
|
||||
if (mPlayPauseAction.getIndex() == PlayPauseAction.PLAY) {
|
||||
mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), 0, false);
|
||||
} else {
|
||||
mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), 0, true);
|
||||
}
|
||||
mFfwRwdSpeed = INITIAL_SPEED;
|
||||
updatePlaybackRow(mCurrentItem);
|
||||
}
|
||||
|
||||
private void prev() {
|
||||
if (--mCurrentItem < 0) {
|
||||
mCurrentItem = mItems.size() - 1;
|
||||
}
|
||||
if (mPlayPauseAction.getIndex() == PlayPauseAction.PLAY) {
|
||||
mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), 0, false);
|
||||
} else {
|
||||
mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), 0, true);
|
||||
}
|
||||
mFfwRwdSpeed = INITIAL_SPEED;
|
||||
updatePlaybackRow(mCurrentItem);
|
||||
}
|
||||
|
||||
private void fastForward() {
|
||||
Log.d(TAG, "current time: " + mPlaybackControlsRow.getCurrentTime());
|
||||
startClickTrackingTimer();
|
||||
int currentTime = mPlaybackControlsRow.getCurrentTime() + mFfwRwdSpeed;
|
||||
if (currentTime > (int) mDuration) {
|
||||
currentTime = (int) mDuration;
|
||||
}
|
||||
fastFR(currentTime);
|
||||
}
|
||||
|
||||
private void fastRewind() {
|
||||
startClickTrackingTimer();
|
||||
int currentTime = mPlaybackControlsRow.getCurrentTime() - mFfwRwdSpeed;
|
||||
if (currentTime < 0 || currentTime > (int) mDuration) {
|
||||
currentTime = 0;
|
||||
}
|
||||
fastFR(currentTime);
|
||||
}
|
||||
|
||||
private void fastFR(int currentTime) {
|
||||
mCallback.onFragmentFfwRwd(mItems.get(mCurrentItem), currentTime);
|
||||
mPlaybackControlsRow.setCurrentTime(currentTime);
|
||||
mPlaybackControlsRow.setBufferedProgress(currentTime + SIMULATED_BUFFERED_TIME);
|
||||
}
|
||||
|
||||
private void stopProgressAutomation() {
|
||||
if (mHandler != null && mRunnable != null) {
|
||||
mHandler.removeCallbacks(mRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
stopProgressAutomation();
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
protected void updateVideoImage(String uri) {
|
||||
Glide.with(sContext)
|
||||
.load(uri)
|
||||
.centerCrop()
|
||||
.into(new SimpleTarget<GlideDrawable>(CARD_WIDTH, CARD_HEIGHT) {
|
||||
@Override
|
||||
public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
|
||||
mPlaybackControlsRow.setImageDrawable(resource);
|
||||
mRowsAdapter.notifyArrayItemRangeChanged(0, mRowsAdapter.size());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void startClickTrackingTimer() {
|
||||
if (null != mClickTrackingTimer) {
|
||||
mClickCount++;
|
||||
mClickTrackingTimer.cancel();
|
||||
} else {
|
||||
mClickCount = 0;
|
||||
mFfwRwdSpeed = INITIAL_SPEED;
|
||||
}
|
||||
mClickTrackingTimer = new Timer();
|
||||
mClickTrackingTimer.schedule(new UpdateFfwRwdSpeedTask(), CLICK_TRACKING_DELAY);
|
||||
}
|
||||
|
||||
// Container Activity must implement this interface
|
||||
public interface OnPlayPauseClickedListener {
|
||||
public void onFragmentPlayPause(Movie movie, int position, Boolean playPause);
|
||||
|
||||
public void onFragmentFfwRwd(Movie movie, int position);
|
||||
}
|
||||
|
||||
static class DescriptionPresenter extends AbstractDetailsDescriptionPresenter {
|
||||
@Override
|
||||
protected void onBindDescription(ViewHolder viewHolder, Object item) {
|
||||
viewHolder.getTitle().setText(((Movie) item).getTitle());
|
||||
viewHolder.getSubtitle().setText(((Movie) item).getStudio());
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateFfwRwdSpeedTask extends TimerTask {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mClickTrackingHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mClickCount == 0) {
|
||||
mFfwRwdSpeed = INITIAL_SPEED;
|
||||
} else if (mClickCount == 1) {
|
||||
mFfwRwdSpeed *= 2;
|
||||
} else if (mClickCount >= 2) {
|
||||
mFfwRwdSpeed *= 4;
|
||||
}
|
||||
mClickCount = 0;
|
||||
mClickTrackingTimer = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private final class ItemViewClickedListener implements OnItemViewClickedListener {
|
||||
@Override
|
||||
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
|
||||
RowPresenter.ViewHolder rowViewHolder, Row row) {
|
||||
|
||||
if (item instanceof Movie) {
|
||||
Movie movie = (Movie) item;
|
||||
Log.d(TAG, "Item: " + item.toString());
|
||||
Intent intent = new Intent(getActivity(), PlaybackOverlayActivity.class);
|
||||
intent.putExtra(MovieDetailsActivity.MOVIE, movie);
|
||||
|
||||
Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||
getActivity(),
|
||||
((ImageCardView) itemViewHolder.view).getMainImageView(),
|
||||
MovieDetailsActivity.SHARED_ELEMENT_NAME).toBundle();
|
||||
getActivity().startActivity(intent, bundle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.tvleanback.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v17.leanback.widget.SpeechRecognitionCallback;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.android.tvleanback.R;
|
||||
|
||||
/*
|
||||
* SearchActivity for SearchFragment
|
||||
*/
|
||||
public class SearchActivity extends Activity {
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
|
||||
private static final String TAG = "SearchActivity";
|
||||
private static boolean DEBUG = true;
|
||||
/**
|
||||
* SpeechRecognitionCallback is not required and if not provided recognition will be handled
|
||||
* using internal speech recognizer, in which case you must have RECORD_AUDIO permission
|
||||
*/
|
||||
private static final int REQUEST_SPEECH = 1;
|
||||
private SearchFragment mFragment;
|
||||
private SpeechRecognitionCallback mSpeechRecognitionCallback;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.search);
|
||||
|
||||
mFragment = (SearchFragment) getFragmentManager().findFragmentById(R.id.search_fragment);
|
||||
|
||||
mSpeechRecognitionCallback = new SpeechRecognitionCallback() {
|
||||
@Override
|
||||
public void recognizeSpeech() {
|
||||
if (DEBUG) Log.v(TAG, "recognizeSpeech");
|
||||
startActivityForResult(mFragment.getRecognizerIntent(), REQUEST_SPEECH);
|
||||
}
|
||||
};
|
||||
mFragment.setSpeechRecognitionCallback(mSpeechRecognitionCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (DEBUG) Log.v(TAG, "onActivityResult requestCode=" + requestCode +
|
||||
" resultCode=" + resultCode +
|
||||
" data=" + data);
|
||||
if (requestCode == REQUEST_SPEECH && resultCode == RESULT_OK) {
|
||||
mFragment.setSearchQuery(data, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSearchRequested() {
|
||||
startActivity(new Intent(this, SearchActivity.class));
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.tvleanback.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v17.leanback.widget.ArrayObjectAdapter;
|
||||
import android.support.v17.leanback.widget.HeaderItem;
|
||||
import android.support.v17.leanback.widget.ImageCardView;
|
||||
import android.support.v17.leanback.widget.ListRow;
|
||||
import android.support.v17.leanback.widget.ListRowPresenter;
|
||||
import android.support.v17.leanback.widget.ObjectAdapter;
|
||||
import android.support.v17.leanback.widget.OnItemViewClickedListener;
|
||||
import android.support.v17.leanback.widget.Presenter;
|
||||
import android.support.v17.leanback.widget.Row;
|
||||
import android.support.v17.leanback.widget.RowPresenter;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.example.android.tvleanback.R;
|
||||
import com.example.android.tvleanback.data.VideoProvider;
|
||||
import com.example.android.tvleanback.model.Movie;
|
||||
import com.example.android.tvleanback.presenter.CardPresenter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* This class demonstrates how to do in-app search
|
||||
*/
|
||||
public class SearchFragment extends android.support.v17.leanback.app.SearchFragment
|
||||
implements android.support.v17.leanback.app.SearchFragment.SearchResultProvider {
|
||||
private static final String TAG = "SearchFragment";
|
||||
private static final int SEARCH_DELAY_MS = 1000;
|
||||
|
||||
private ArrayObjectAdapter mRowsAdapter;
|
||||
private Handler mHandler = new Handler();
|
||||
private SearchRunnable mDelayedLoad;
|
||||
private String mQuery;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
|
||||
setSearchResultProvider(this);
|
||||
setOnItemViewClickedListener(new ItemViewClickedListener());
|
||||
mDelayedLoad = new SearchRunnable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectAdapter getResultsAdapter() {
|
||||
return mRowsAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newQuery) {
|
||||
Log.i(TAG, String.format("Search Query Text Change %s", newQuery));
|
||||
loadQuery(newQuery);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
Log.i(TAG, String.format("Search Query Text Submit %s", query));
|
||||
loadQuery(query);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void loadQuery(String query) {
|
||||
mQuery = query;
|
||||
mRowsAdapter.clear();
|
||||
mHandler.removeCallbacks(mDelayedLoad);
|
||||
if (!TextUtils.isEmpty(query) && !query.equals("nil")) {
|
||||
mDelayedLoad.setSearchQuery(query);
|
||||
mHandler.postDelayed(mDelayedLoad, SEARCH_DELAY_MS);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadRows(String query) {
|
||||
HashMap<String, List<Movie>> movies = VideoProvider.getMovieList();
|
||||
ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
|
||||
for (Map.Entry<String, List<Movie>> entry : movies.entrySet()) {
|
||||
for (Movie movie : entry.getValue()) {
|
||||
if (movie.getTitle().toLowerCase(Locale.ENGLISH)
|
||||
.contains(query.toLowerCase(Locale.ENGLISH))
|
||||
|| movie.getDescription().toLowerCase(Locale.ENGLISH)
|
||||
.contains(query.toLowerCase(Locale.ENGLISH))) {
|
||||
listRowAdapter.add(movie);
|
||||
}
|
||||
}
|
||||
}
|
||||
HeaderItem header = new HeaderItem(0, getResources().getString(R.string.search_results)
|
||||
+ " '" + mQuery + "'");
|
||||
mRowsAdapter.add(new ListRow(header, listRowAdapter));
|
||||
}
|
||||
|
||||
private class SearchRunnable implements Runnable {
|
||||
|
||||
private volatile String searchQuery;
|
||||
|
||||
public SearchRunnable() {
|
||||
}
|
||||
|
||||
public void run() {
|
||||
loadRows(searchQuery);
|
||||
}
|
||||
|
||||
public void setSearchQuery(String value) {
|
||||
this.searchQuery = value;
|
||||
}
|
||||
}
|
||||
|
||||
private final class ItemViewClickedListener implements OnItemViewClickedListener {
|
||||
@Override
|
||||
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
|
||||
RowPresenter.ViewHolder rowViewHolder, Row row) {
|
||||
|
||||
if (item instanceof Movie) {
|
||||
Movie movie = (Movie) item;
|
||||
Log.d(TAG, "Movie: " + movie.toString());
|
||||
Intent intent = new Intent(getActivity(), MovieDetailsActivity.class);
|
||||
intent.putExtra(MovieDetailsActivity.MOVIE, movie);
|
||||
|
||||
Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||
getActivity(),
|
||||
((ImageCardView) itemViewHolder.view).getMainImageView(),
|
||||
MovieDetailsActivity.SHARED_ELEMENT_NAME).toBundle();
|
||||
getActivity().startActivity(intent, bundle);
|
||||
} else {
|
||||
Toast.makeText(getActivity(), ((String) item), Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.tvleanback.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.example.android.tvleanback.R;
|
||||
|
||||
/*
|
||||
* VerticalGridActivity that loads VerticalGridFragment
|
||||
*/
|
||||
public class VerticalGridActivity extends Activity {
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.vertical_grid);
|
||||
getWindow().setBackgroundDrawableResource(R.drawable.grid_bg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSearchRequested() {
|
||||
startActivity(new Intent(this, SearchActivity.class));
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.tvleanback.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v17.leanback.widget.ArrayObjectAdapter;
|
||||
import android.support.v17.leanback.widget.ImageCardView;
|
||||
import android.support.v17.leanback.widget.OnItemViewClickedListener;
|
||||
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
|
||||
import android.support.v17.leanback.widget.Presenter;
|
||||
import android.support.v17.leanback.widget.Row;
|
||||
import android.support.v17.leanback.widget.RowPresenter;
|
||||
import android.support.v17.leanback.widget.VerticalGridPresenter;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import com.example.android.tvleanback.R;
|
||||
import com.example.android.tvleanback.data.VideoProvider;
|
||||
import com.example.android.tvleanback.model.Movie;
|
||||
import com.example.android.tvleanback.presenter.CardPresenter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
/*
|
||||
* VerticalGridFragment shows a grid of videos
|
||||
*/
|
||||
public class VerticalGridFragment extends android.support.v17.leanback.app.VerticalGridFragment {
|
||||
private static final String TAG = "VerticalGridFragment";
|
||||
|
||||
private static final int NUM_COLUMNS = 5;
|
||||
|
||||
private ArrayObjectAdapter mAdapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
Log.d(TAG, "onCreate");
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setTitle(getString(R.string.vertical_grid_title));
|
||||
|
||||
setupFragment();
|
||||
}
|
||||
|
||||
private void setupFragment() {
|
||||
VerticalGridPresenter gridPresenter = new VerticalGridPresenter();
|
||||
gridPresenter.setNumberOfColumns(NUM_COLUMNS);
|
||||
setGridPresenter(gridPresenter);
|
||||
|
||||
mAdapter = new ArrayObjectAdapter(new CardPresenter());
|
||||
|
||||
long seed = System.nanoTime();
|
||||
|
||||
HashMap<String, List<Movie>> movies = VideoProvider.getMovieList();
|
||||
|
||||
for (Map.Entry<String, List<Movie>> entry : movies.entrySet()) {
|
||||
List<Movie> list = entry.getValue();
|
||||
Collections.shuffle(list, new Random(seed));
|
||||
for (Movie movie : list) {
|
||||
mAdapter.add(movie);
|
||||
}
|
||||
}
|
||||
|
||||
setAdapter(mAdapter);
|
||||
|
||||
setOnSearchClickedListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent intent = new Intent(getActivity(), SearchActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
setOnItemViewClickedListener(new ItemViewClickedListener());
|
||||
setOnItemViewSelectedListener(new ItemViewSelectedListener());
|
||||
}
|
||||
|
||||
private final class ItemViewClickedListener implements OnItemViewClickedListener {
|
||||
@Override
|
||||
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
|
||||
RowPresenter.ViewHolder rowViewHolder, Row row) {
|
||||
|
||||
if (item instanceof Movie) {
|
||||
Movie movie = (Movie) item;
|
||||
Log.d(TAG, "Item: " + item.toString());
|
||||
Intent intent = new Intent(getActivity(), MovieDetailsActivity.class);
|
||||
intent.putExtra(MovieDetailsActivity.MOVIE, movie);
|
||||
|
||||
Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||
getActivity(),
|
||||
((ImageCardView) itemViewHolder.view).getMainImageView(),
|
||||
MovieDetailsActivity.SHARED_ELEMENT_NAME).toBundle();
|
||||
getActivity().startActivity(intent, bundle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final class ItemViewSelectedListener implements OnItemViewSelectedListener {
|
||||
@Override
|
||||
public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
|
||||
RowPresenter.ViewHolder rowViewHolder, Row row) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 1,003 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 9.2 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 707 B |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 750 B |
After Width: | Height: | Size: 1 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 535 B |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 391 KiB |
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:startColor="@color/background_gradient_start"
|
||||
android:endColor="@color/background_gradient_end"
|
||||
android:angle="-270" />
|
||||
</shape>
|
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 391 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 1,004 B |
After Width: | Height: | Size: 677 B |
After Width: | Height: | Size: 885 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 464 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2.4 KiB |