text:
// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cc/resources/resource_provider.h"

#include <algorithm>
#include <limits>

#include "base/containers/hash_tables.h"
#include "base/debug/alias.h"
#include "base/stl_util.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "cc/output/gl_renderer.h"  // For the GLC() macro.
#include "cc/resources/platform_color.h"
#include "cc/resources/transferable_resource.h"
#include "cc/scheduler/texture_uploader.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
#include "third_party/khronos/GLES2/gl2.h"
#include "third_party/khronos/GLES2/gl2ext.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/vector2d.h"

using WebKit::WebGraphicsContext3D;

namespace cc {

namespace {

GLenum TextureToStorageFormat(GLenum texture_format) {
  GLenum storage_format = GL_RGBA8_OES;
  switch (texture_format) {
    case GL_RGBA:
      break;
    case GL_BGRA_EXT:
      storage_format = GL_BGRA8_EXT;
      break;
    default:
      NOTREACHED();
      break;
  }

  return storage_format;
}

bool IsTextureFormatSupportedForStorage(GLenum format) {
  return (format == GL_RGBA || format == GL_BGRA_EXT);
}

unsigned CreateTextureId(WebGraphicsContext3D* context3d) {
  unsigned texture_id = 0;
  GLC(context3d, texture_id = context3d->createTexture());
  GLC(context3d, context3d->bindTexture(GL_TEXTURE_2D, texture_id));
  GLC(context3d, context3d->texParameteri(
      GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
  GLC(context3d, context3d->texParameteri(
      GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
  GLC(context3d, context3d->texParameteri(
      GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
  GLC(context3d, context3d->texParameteri(
      GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
  return texture_id;
}

}  // namespace

ResourceProvider::Resource::Resource()
    : gl_id(0),
      gl_pixel_buffer_id(0),
      gl_upload_query_id(0),
      pixels(NULL),
      pixel_buffer(NULL),
      lock_for_read_count(0),
      locked_for_write(false),
      external(false),
      exported(false),
      marked_for_deletion(false),
      pending_set_pixels(false),
      set_pixels_completion_forced(false),
      allocated(false),
      enable_read_lock_fences(false),
      read_lock_fence(NULL),
      size(),
      format(0),
      filter(0),
      image_id(0),
      texture_pool(0),
      hint(TextureUsageAny),
      type(static_cast<ResourceType>(0)) {}

ResourceProvider::Resource::~Resource() {}

ResourceProvider::Resource::Resource(
    unsigned texture_id,
    gfx::Size size,
    GLenum format,
    GLenum filter,
    GLenum texture_pool,
    TextureUsageHint hint)
    : gl_id(texture_id),
      gl_pixel_buffer_id(0),
      gl_upload_query_id(0),
      pixels(NULL),
      pixel_buffer(NULL),
      lock_for_read_count(0),
      locked_for_write(false),
      external(false),
      exported(false),
      marked_for_deletion(false),
      pending_set_pixels(false),
      set_pixels_completion_forced(false),
      allocated(false),
      enable_read_lock_fences(false),
      read_lock_fence(NULL),
      size(size),
      format(format),
      filter(filter),
      image_id(0),
      texture_pool(texture_pool),
      hint(hint),
      type(GLTexture) {}

ResourceProvider::Resource::Resource(
    uint8_t* pixels, gfx::Size size, GLenum format, GLenum filter)
    : gl_id(0),
      gl_pixel_buffer_id(0),
      gl_upload_query_id(0),
      pixels(pixels),
      pixel_buffer(NULL),
      lock_for_read_count(0),
      locked_for_write(false),
      external(false),
      exported(false),
      marked_for_deletion(false),
      pending_set_pixels(false),
      set_pixels_completion_forced(false),
      allocated(false),
      enable_read_lock_fences(false),
      read_lock_fence(NULL),
      size(size),
      format(format),
      filter(filter),
      image_id(0),
      texture_pool(0),
      hint(TextureUsageAny),
      type(Bitmap) {}

ResourceProvider::Child::Child() {}

ResourceProvider::Child::~Child() {}

scoped_ptr<ResourceProvider> ResourceProvider::Create(
    OutputSurface* output_surface,
    int highp_threshold_min) {
  scoped_ptr<ResourceProvider> resource_provider(
      new ResourceProvider(output_surface));
  if (!resource_provider->Initialize(highp_threshold_min))
    return scoped_ptr<ResourceProvider>();
  return resource_provider.Pass();
}

ResourceProvider::~ResourceProvider() {
  while (!resources_.empty())
    DeleteResourceInternal(resources_.begin(), ForShutdown);

  WebGraphicsContext3D* context3d = output_surface_->context3d();
  if (!context3d || !context3d->makeContextCurrent())
    return;
  texture_uploader_.reset();
  texture_copier_.reset();
}

WebGraphicsContext3D* ResourceProvider::GraphicsContext3D() {
  DCHECK(thread_checker_.CalledOnValidThread());
  return output_surface_->context3d();
}

bool ResourceProvider::InUseByConsumer(ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;
  return !!resource->lock_for_read_count || resource->exported;
}

ResourceProvider::ResourceId ResourceProvider::CreateResource(
    gfx::Size size, GLenum format, TextureUsageHint hint) {
  DCHECK(!size.IsEmpty());
  switch (default_resource_type_) {
    case GLTexture:
      return CreateGLTexture(
          size, format, GL_TEXTURE_POOL_UNMANAGED_CHROMIUM, hint);
    case Bitmap:
      DCHECK(format == GL_RGBA);
      return CreateBitmap(size);
  }

  LOG(FATAL) << "Invalid default resource type.";
  return 0;
}

ResourceProvider::ResourceId ResourceProvider::CreateManagedResource(
    gfx::Size size, GLenum format, TextureUsageHint hint) {
  DCHECK(!size.IsEmpty());
  switch (default_resource_type_) {
    case GLTexture:
      return CreateGLTexture(
          size, format, GL_TEXTURE_POOL_MANAGED_CHROMIUM, hint);
    case Bitmap:
      DCHECK(format == GL_RGBA);
      return CreateBitmap(size);
  }

  LOG(FATAL) << "Invalid default resource type.";
  return 0;
}

ResourceProvider::ResourceId ResourceProvider::CreateGLTexture(
    gfx::Size size, GLenum format, GLenum texture_pool, TextureUsageHint hint) {
  DCHECK_LE(size.width(), max_texture_size_);
  DCHECK_LE(size.height(), max_texture_size_);
  DCHECK(thread_checker_.CalledOnValidThread());

  ResourceId id = next_id_++;
  Resource resource(0, size, format, GL_LINEAR, texture_pool, hint);
  resource.allocated = false;
  resources_[id] = resource;
  return id;
}

ResourceProvider::ResourceId ResourceProvider::CreateBitmap(gfx::Size size) {
  DCHECK(thread_checker_.CalledOnValidThread());

  uint8_t* pixels = new uint8_t[4 * size.GetArea()];

  ResourceId id = next_id_++;
  Resource resource(pixels, size, GL_RGBA, GL_LINEAR);
  resource.allocated = true;
  resources_[id] = resource;
  return id;
}

ResourceProvider::ResourceId
ResourceProvider::CreateResourceFromExternalTexture(
    unsigned texture_target,
    unsigned texture_id) {
  DCHECK(thread_checker_.CalledOnValidThread());

  WebGraphicsContext3D* context3d = output_surface_->context3d();
  DCHECK(context3d);
  GLC(context3d, context3d->bindTexture(texture_target, texture_id));
  GLC(context3d, context3d->texParameteri(
      texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
  GLC(context3d, context3d->texParameteri(
      texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
  GLC(context3d, context3d->texParameteri(
      texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
  GLC(context3d, context3d->texParameteri(
      texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));

  ResourceId id = next_id_++;
  Resource resource(texture_id, gfx::Size(), 0, GL_LINEAR, 0, TextureUsageAny);
  resource.external = true;
  resource.allocated = true;
  resources_[id] = resource;
  return id;
}

ResourceProvider::ResourceId ResourceProvider::CreateResourceFromTextureMailbox(
    const TextureMailbox& mailbox) {
  DCHECK(thread_checker_.CalledOnValidThread());
  // Just store the information. Mailbox will be consumed in LockForRead().
  ResourceId id = next_id_++;
  DCHECK(mailbox.IsValid());
  Resource& resource = resources_[id];
  if (mailbox.IsTexture()) {
    resource = Resource(0, gfx::Size(), 0, GL_LINEAR, 0, TextureUsageAny);
  } else {
    DCHECK(mailbox.IsSharedMemory());
    base::SharedMemory* shared_memory = mailbox.shared_memory();
    DCHECK(shared_memory->memory());
    uint8_t* pixels = reinterpret_cast<uint8_t*>(shared_memory->memory());
    resource = Resource(pixels, mailbox.shared_memory_size(),
                        GL_RGBA, GL_LINEAR);
  }
  resource.external = true;
  resource.allocated = true;
  resource.mailbox = mailbox;
  return id;
}

void ResourceProvider::DeleteResource(ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;
  DCHECK(!resource->lock_for_read_count);
  DCHECK(!resource->marked_for_deletion);
  DCHECK(resource->pending_set_pixels || !resource->locked_for_write);

  if (resource->exported) {
    resource->marked_for_deletion = true;
    return;
  } else {
    DeleteResourceInternal(it, Normal);
  }
}

void ResourceProvider::DeleteResourceInternal(ResourceMap::iterator it,
                                              DeleteStyle style) {
  Resource* resource = &it->second;
  bool lost_resource = lost_output_surface_;

  DCHECK(!resource->exported || style != Normal);
  if (style == ForShutdown && resource->exported)
    lost_resource = true;

  if (resource->image_id) {
    WebGraphicsContext3D* context3d = output_surface_->context3d();
    DCHECK(context3d);
    GLC(context3d, context3d->destroyImageCHROMIUM(resource->image_id));
  }

  if (resource->gl_id && !resource->external) {
    WebGraphicsContext3D* context3d = output_surface_->context3d();
    DCHECK(context3d);
    GLC(context3d, context3d->deleteTexture(resource->gl_id));
  }
  if (resource->gl_upload_query_id) {
    WebGraphicsContext3D* context3d = output_surface_->context3d();
    DCHECK(context3d);
    GLC(context3d, context3d->deleteQueryEXT(resource->gl_upload_query_id));
  }
  if (resource->gl_pixel_buffer_id) {
    WebGraphicsContext3D* context3d = output_surface_->context3d();
    DCHECK(context3d);
    GLC(context3d, context3d->deleteBuffer(resource->gl_pixel_buffer_id));
  }
  if (resource->mailbox.IsValid() && resource->external) {
    unsigned sync_point = resource->mailbox.sync_point();
    if (resource->mailbox.IsTexture()) {
      WebGraphicsContext3D* context3d = output_surface_->context3d();
      DCHECK(context3d);
      if (!lost_resource && resource->gl_id) {
        GLC(context3d, context3d->bindTexture(
            resource->mailbox.target(), resource->gl_id));
        GLC(context3d, context3d->produceTextureCHROMIUM(
            resource->mailbox.target(), resource->mailbox.data()));
      }
      if (resource->gl_id)
        GLC(context3d, context3d->deleteTexture(resource->gl_id));
      if (!lost_resource && resource->gl_id)
        sync_point = context3d->insertSyncPoint();
    } else {
      DCHECK(resource->mailbox.IsSharedMemory());
      base::SharedMemory* shared_memory = resource->mailbox.shared_memory();
      if (resource->pixels && shared_memory) {
        DCHECK(shared_memory->memory() == resource->pixels);
        resource->pixels = NULL;
      }
    }
    resource->mailbox.RunReleaseCallback(sync_point, lost_resource);
  }
  if (resource->pixels)
    delete[] resource->pixels;
  if (resource->pixel_buffer)
    delete[] resource->pixel_buffer;

  resources_.erase(it);
}

ResourceProvider::ResourceType ResourceProvider::GetResourceType(
    ResourceId id) {
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;
  return resource->type;
}

void ResourceProvider::SetPixels(ResourceId id,
                                 const uint8_t* image,
                                 gfx::Rect image_rect,
                                 gfx::Rect source_rect,
                                 gfx::Vector2d dest_offset) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;
  DCHECK(!resource->locked_for_write);
  DCHECK(!resource->lock_for_read_count);
  DCHECK(!resource->external);
  DCHECK(!resource->exported);
  DCHECK(ReadLockFenceHasPassed(resource));
  LazyAllocate(resource);

  if (resource->gl_id) {
    DCHECK(!resource->pending_set_pixels);
    WebGraphicsContext3D* context3d = output_surface_->context3d();
    DCHECK(context3d);
    DCHECK(texture_uploader_.get());
    context3d->bindTexture(GL_TEXTURE_2D, resource->gl_id);
    texture_uploader_->Upload(image,
                              image_rect,
                              source_rect,
                              dest_offset,
                              resource->format,
                              resource->size);
  }

  if (resource->pixels) {
    DCHECK(resource->allocated);
    DCHECK(resource->format == GL_RGBA);
    SkBitmap src_full;
    src_full.setConfig(
        SkBitmap::kARGB_8888_Config, image_rect.width(), image_rect.height());
    src_full.setPixels(const_cast<uint8_t*>(image));
    SkBitmap src_subset;
    SkIRect sk_source_rect = SkIRect::MakeXYWH(source_rect.x(),
                                               source_rect.y(),
                                               source_rect.width(),
                                               source_rect.height());
    sk_source_rect.offset(-image_rect.x(), -image_rect.y());
    src_full.extractSubset(&src_subset, sk_source_rect);

    ScopedWriteLockSoftware lock(this, id);
    SkCanvas* dest = lock.sk_canvas();
    dest->writePixels(src_subset, dest_offset.x(), dest_offset.y());
  }
}

size_t ResourceProvider::NumBlockingUploads() {
  if (!texture_uploader_)
    return 0;

  return texture_uploader_->NumBlockingUploads();
}

void ResourceProvider::MarkPendingUploadsAsNonBlocking() {
  if (!texture_uploader_)
    return;

  texture_uploader_->MarkPendingUploadsAsNonBlocking();
}

double ResourceProvider::EstimatedUploadsPerSecond() {
  if (!texture_uploader_)
    return 0.0;

  return texture_uploader_->EstimatedTexturesPerSecond();
}

void ResourceProvider::FlushUploads() {
  if (!texture_uploader_)
    return;

  texture_uploader_->Flush();
}

void ResourceProvider::ReleaseCachedData() {
  if (!texture_uploader_)
    return;

  texture_uploader_->ReleaseCachedQueries();
}

void ResourceProvider::Flush() {
  DCHECK(thread_checker_.CalledOnValidThread());
  WebGraphicsContext3D* context3d = output_surface_->context3d();
  if (context3d)
    context3d->flush();
}

void ResourceProvider::Finish() {
  DCHECK(thread_checker_.CalledOnValidThread());
  WebGraphicsContext3D* context3d = output_surface_->context3d();
  if (context3d)
    context3d->finish();
}

bool ResourceProvider::ShallowFlushIfSupported() {
  DCHECK(thread_checker_.CalledOnValidThread());
  WebGraphicsContext3D* context3d = output_surface_->context3d();
  if (!context3d || !use_shallow_flush_)
    return false;

  context3d->shallowFlushCHROMIUM();
  return true;
}

const ResourceProvider::Resource* ResourceProvider::LockForRead(ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;
  DCHECK(!resource->locked_for_write ||
         resource->set_pixels_completion_forced) <<
      "locked for write: " << resource->locked_for_write <<
      " pixels completion forced: " << resource->set_pixels_completion_forced;
  DCHECK(!resource->exported);
  // Uninitialized! Call SetPixels or LockForWrite first.
  DCHECK(resource->allocated);

  if (resource->external) {
    if (!resource->gl_id && resource->mailbox.IsTexture()) {
      WebGraphicsContext3D* context3d = output_surface_->context3d();
      DCHECK(context3d);
      if (resource->mailbox.sync_point()) {
        GLC(context3d,
            context3d->waitSyncPoint(resource->mailbox.sync_point()));
        resource->mailbox.ResetSyncPoint();
      }
      resource->gl_id = context3d->createTexture();
      GLC(context3d, context3d->bindTexture(
          resource->mailbox.target(), resource->gl_id));
      GLC(context3d, context3d->consumeTextureCHROMIUM(
          resource->mailbox.target(), resource->mailbox.data()));
    }
  }

  resource->lock_for_read_count++;
  if (resource->enable_read_lock_fences)
    resource->read_lock_fence = current_read_lock_fence_;

  return resource;
}

void ResourceProvider::UnlockForRead(ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;
  DCHECK_GT(resource->lock_for_read_count, 0);
  DCHECK(!resource->exported);
  resource->lock_for_read_count--;
}

const ResourceProvider::Resource* ResourceProvider::LockForWrite(
    ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;
  DCHECK(!resource->locked_for_write);
  DCHECK(!resource->lock_for_read_count);
  DCHECK(!resource->exported);
  DCHECK(!resource->external);
  DCHECK(ReadLockFenceHasPassed(resource));
  LazyAllocate(resource);

  resource->locked_for_write = true;
  return resource;
}

bool ResourceProvider::CanLockForWrite(ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;
  return !resource->locked_for_write &&
      !resource->lock_for_read_count &&
      !resource->exported &&
      !resource->external &&
      ReadLockFenceHasPassed(resource);
}

void ResourceProvider::UnlockForWrite(ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;
  DCHECK(resource->locked_for_write);
  DCHECK(!resource->exported);
  DCHECK(!resource->external);
  resource->locked_for_write = false;
}

ResourceProvider::ScopedReadLockGL::ScopedReadLockGL(
    ResourceProvider* resource_provider,
    ResourceProvider::ResourceId resource_id)
    : resource_provider_(resource_provider),
      resource_id_(resource_id),
      texture_id_(resource_provider->LockForRead(resource_id)->gl_id) {
  DCHECK(texture_id_);
}

ResourceProvider::ScopedReadLockGL::~ScopedReadLockGL() {
  resource_provider_->UnlockForRead(resource_id_);
}

ResourceProvider::ScopedSamplerGL::ScopedSamplerGL(
    ResourceProvider* resource_provider,
    ResourceProvider::ResourceId resource_id,
    GLenum target,
    GLenum filter)
    : ScopedReadLockGL(resource_provider, resource_id) {
  resource_provider->BindForSampling(resource_id, target, filter);
}

ResourceProvider::ScopedWriteLockGL::ScopedWriteLockGL(
    ResourceProvider* resource_provider,
    ResourceProvider::ResourceId resource_id)
    : resource_provider_(resource_provider),
      resource_id_(resource_id),
      texture_id_(resource_provider->LockForWrite(resource_id)->gl_id) {
  DCHECK(texture_id_);
}

ResourceProvider::ScopedWriteLockGL::~ScopedWriteLockGL() {
  resource_provider_->UnlockForWrite(resource_id_);
}

void ResourceProvider::PopulateSkBitmapWithResource(
    SkBitmap* sk_bitmap, const Resource* resource) {
  DCHECK(resource->pixels);
  DCHECK(resource->format == GL_RGBA);
  sk_bitmap->setConfig(SkBitmap::kARGB_8888_Config,
                       resource->size.width(),
                       resource->size.height());
  sk_bitmap->setPixels(resource->pixels);
}

ResourceProvider::ScopedReadLockSoftware::ScopedReadLockSoftware(
    ResourceProvider* resource_provider,
    ResourceProvider::ResourceId resource_id)
    : resource_provider_(resource_provider),
      resource_id_(resource_id) {
  ResourceProvider::PopulateSkBitmapWithResource(
      &sk_bitmap_, resource_provider->LockForRead(resource_id));
}

ResourceProvider::ScopedReadLockSoftware::~ScopedReadLockSoftware() {
  resource_provider_->UnlockForRead(resource_id_);
}

ResourceProvider::ScopedWriteLockSoftware::ScopedWriteLockSoftware(
    ResourceProvider* resource_provider,
    ResourceProvider::ResourceId resource_id)
    : resource_provider_(resource_provider),
      resource_id_(resource_id) {
  ResourceProvider::PopulateSkBitmapWithResource(
      &sk_bitmap_, resource_provider->LockForWrite(resource_id));
  sk_canvas_.reset(new SkCanvas(sk_bitmap_));
}

ResourceProvider::ScopedWriteLockSoftware::~ScopedWriteLockSoftware() {
  resource_provider_->UnlockForWrite(resource_id_);
}

ResourceProvider::ResourceProvider(OutputSurface* output_surface)
    : output_surface_(output_surface),
      lost_output_surface_(false),
      next_id_(1),
      next_child_(1),
      default_resource_type_(output_surface->context3d() ? GLTexture : Bitmap),
      use_texture_storage_ext_(false),
      use_texture_usage_hint_(false),
      use_shallow_flush_(false),
      max_texture_size_(0),
      best_texture_format_(0) {
}

bool ResourceProvider::Initialize(int highp_threshold_min) {
  DCHECK(thread_checker_.CalledOnValidThread());
  WebGraphicsContext3D* context3d = output_surface_->context3d();
  if (!context3d) {
    max_texture_size_ = INT_MAX / 2;
    best_texture_format_ = GL_RGBA;
    return true;
  }
  if (!context3d->makeContextCurrent())
    return false;

  std::string extensions_string =
      UTF16ToASCII(context3d->getString(GL_EXTENSIONS));
  std::vector<std::string> extensions;
  base::SplitString(extensions_string, ' ', &extensions);
  bool use_map_sub = false;
  bool use_bind_uniform = false;
  bool use_bgra = false;
  for (size_t i = 0; i < extensions.size(); ++i) {
    if (extensions[i] == "GL_EXT_texture_storage")
      use_texture_storage_ext_ = true;
    else if (extensions[i] == "GL_ANGLE_texture_usage")
      use_texture_usage_hint_ = true;
    else if (extensions[i] == "GL_CHROMIUM_map_sub")
      use_map_sub = true;
    else if (extensions[i] == "GL_CHROMIUM_shallow_flush")
      use_shallow_flush_ = true;
    else if (extensions[i] == "GL_CHROMIUM_bind_uniform_location")
      use_bind_uniform = true;
    else if (extensions[i] == "GL_EXT_texture_format_BGRA8888")
      use_bgra = true;
  }

  texture_copier_ = AcceleratedTextureCopier::Create(
      context3d, use_bind_uniform, highp_threshold_min);

  texture_uploader_ =
      TextureUploader::Create(context3d, use_map_sub, use_shallow_flush_);
  GLC(context3d, context3d->getIntegerv(GL_MAX_TEXTURE_SIZE,
                                        &max_texture_size_));
  best_texture_format_ =
      PlatformColor::BestTextureFormat(context3d, use_bgra);
  return true;
}

int ResourceProvider::CreateChild() {
  DCHECK(thread_checker_.CalledOnValidThread());
  Child child_info;
  int child = next_child_++;
  children_[child] = child_info;
  return child;
}

void ResourceProvider::DestroyChild(int child_id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ChildMap::iterator it = children_.find(child_id);
  DCHECK(it != children_.end());
  Child& child = it->second;
  for (ResourceIdMap::iterator child_it = child.child_to_parent_map.begin();
       child_it != child.child_to_parent_map.end();
       ++child_it)
    DeleteResource(child_it->second);
  children_.erase(it);
}

const ResourceProvider::ResourceIdMap& ResourceProvider::GetChildToParentMap(
    int child) const {
  DCHECK(thread_checker_.CalledOnValidThread());
  ChildMap::const_iterator it = children_.find(child);
  DCHECK(it != children_.end());
  return it->second.child_to_parent_map;
}

void ResourceProvider::PrepareSendToParent(const ResourceIdArray& resources,
                                           TransferableResourceArray* list) {
  DCHECK(thread_checker_.CalledOnValidThread());
  list->clear();
  WebGraphicsContext3D* context3d = output_surface_->context3d();
  if (!context3d || !context3d->makeContextCurrent()) {
    // FIXME: Implement this path for software compositing.
    return;
  }
  bool need_sync_point = false;
  for (ResourceIdArray::const_iterator it = resources.begin();
       it != resources.end();
       ++it) {
    TransferableResource resource;
    if (TransferResource(context3d, *it, &resource)) {
      if (!resource.sync_point)
        need_sync_point = true;
      resources_.find(*it)->second.exported = true;
      list->push_back(resource);
    }
  }
  if (need_sync_point) {
    unsigned int sync_point = context3d->insertSyncPoint();
    for (TransferableResourceArray::iterator it = list->begin();
         it != list->end();
         ++it) {
      if (!it->sync_point)
        it->sync_point = sync_point;
    }
  }
}

void ResourceProvider::PrepareSendToChild(int child,
                                          const ResourceIdArray& resources,
                                          TransferableResourceArray* list) {
  DCHECK(thread_checker_.CalledOnValidThread());
  list->clear();
  WebGraphicsContext3D* context3d = output_surface_->context3d();
  if (!context3d || !context3d->makeContextCurrent()) {
    // FIXME: Implement this path for software compositing.
    return;
  }
  Child& child_info = children_.find(child)->second;
  bool need_sync_point = false;
  for (ResourceIdArray::const_iterator it = resources.begin();
       it != resources.end();
       ++it) {
    TransferableResource resource;
    if (!TransferResource(context3d, *it, &resource))
      NOTREACHED();
    if (!resource.sync_point)
      need_sync_point = true;
    DCHECK(child_info.parent_to_child_map.find(*it) !=
           child_info.parent_to_child_map.end());
    resource.id = child_info.parent_to_child_map[*it];
    child_info.parent_to_child_map.erase(*it);
    child_info.child_to_parent_map.erase(resource.id);
    list->push_back(resource);
    DeleteResource(*it);
  }
  if (need_sync_point) {
    unsigned int sync_point = context3d->insertSyncPoint();
    for (TransferableResourceArray::iterator it = list->begin();
         it != list->end();
         ++it) {
      if (!it->sync_point)
        it->sync_point = sync_point;
    }
  }
}

void ResourceProvider::ReceiveFromChild(
    int child, const TransferableResourceArray& resources) {
  DCHECK(thread_checker_.CalledOnValidThread());
  WebGraphicsContext3D* context3d = output_surface_->context3d();
  if (!context3d || !context3d->makeContextCurrent()) {
    // FIXME: Implement this path for software compositing.
    return;
  }
  Child& child_info = children_.find(child)->second;
  for (TransferableResourceArray::const_iterator it = resources.begin();
       it != resources.end();
       ++it) {
    unsigned texture_id;
    // NOTE: If the parent is a browser and the child a renderer, the parent
    // is not supposed to have its context wait, because that could induce
    // deadlocks and/or security issues. The caller is responsible for
    // waiting asynchronously, and resetting sync_point before calling this.
    // However if the parent is a renderer (e.g. browser tag), it may be ok
    // (and is simpler) to wait.
    if (it->sync_point)
      GLC(context3d, context3d->waitSyncPoint(it->sync_point));
    GLC(context3d, texture_id = context3d->createTexture());
    GLC(context3d, context3d->bindTexture(GL_TEXTURE_2D, texture_id));
    GLC(context3d, context3d->consumeTextureCHROMIUM(GL_TEXTURE_2D,
                                                     it->mailbox.name));
    ResourceId id = next_id_++;
    Resource resource(
        texture_id, it->size, it->format, it->filter, 0, TextureUsageAny);
    resource.mailbox.SetName(it->mailbox);
    // Don't allocate a texture for a child.
    resource.allocated = true;
    resources_[id] = resource;
    child_info.parent_to_child_map[id] = it->id;
    child_info.child_to_parent_map[it->id] = id;
  }
}

void ResourceProvider::ReceiveFromParent(
    const TransferableResourceArray& resources) {
  DCHECK(thread_checker_.CalledOnValidThread());
  WebGraphicsContext3D* context3d = output_surface_->context3d();
  if (!context3d || !context3d->makeContextCurrent()) {
    // FIXME: Implement this path for software compositing.
    return;
  }
  for (TransferableResourceArray::const_iterator it = resources.begin();
       it != resources.end();
       ++it) {
    ResourceMap::iterator map_iterator = resources_.find(it->id);
    DCHECK(map_iterator != resources_.end());
    Resource* resource = &map_iterator->second;
    DCHECK(resource->exported);
    resource->exported = false;
    resource->filter = it->filter;
    DCHECK(resource->mailbox.ContainsMailbox(it->mailbox));
    if (resource->gl_id) {
      if (it->sync_point)
        GLC(context3d, context3d->waitSyncPoint(it->sync_point));
      GLC(context3d, context3d->bindTexture(GL_TEXTURE_2D, resource->gl_id));
      GLC(context3d, context3d->consumeTextureCHROMIUM(GL_TEXTURE_2D,
                                                       it->mailbox.name));
    } else {
      resource->mailbox = TextureMailbox(resource->mailbox.name(),
                                         resource->mailbox.callback(),
                                         it->sync_point);
    }
    if (resource->marked_for_deletion)
      DeleteResourceInternal(map_iterator, Normal);
  }
}

bool ResourceProvider::TransferResource(WebGraphicsContext3D* context,
                                        ResourceId id,
                                        TransferableResource* resource) {
  DCHECK(thread_checker_.CalledOnValidThread());
  WebGraphicsContext3D* context3d = output_surface_->context3d();
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* source = &it->second;
  DCHECK(!source->locked_for_write);
  DCHECK(!source->lock_for_read_count);
  DCHECK(!source->external || (source->external && source->mailbox.IsValid()));
  DCHECK(source->allocated);
  if (source->exported)
    return false;
  resource->id = id;
  resource->format = source->format;
  resource->filter = source->filter;
  resource->size = source->size;

  // TODO(skaslev) Implement this path for shared memory resources.
  DCHECK(!source->mailbox.IsSharedMemory());

  if (!source->mailbox.IsTexture()) {
    GLC(context3d, context3d->genMailboxCHROMIUM(resource->mailbox.name));
    source->mailbox.SetName(resource->mailbox);
  } else {
    resource->mailbox = source->mailbox.name();
  }

  if (source->gl_id) {
    GLC(context, context->bindTexture(GL_TEXTURE_2D, source->gl_id));
    GLC(context, context->produceTextureCHROMIUM(GL_TEXTURE_2D,
                                                 resource->mailbox.name));
  } else {
    resource->sync_point = source->mailbox.sync_point();
    source->mailbox.ResetSyncPoint();
  }
  return true;
}

void ResourceProvider::AcquirePixelBuffer(ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;
  DCHECK(!resource->external);
  DCHECK(!resource->exported);
  DCHECK(!resource->image_id);

  if (resource->type == GLTexture) {
    WebGraphicsContext3D* context3d = output_surface_->context3d();
    DCHECK(context3d);
    if (!resource->gl_pixel_buffer_id)
      resource->gl_pixel_buffer_id = context3d->createBuffer();
    context3d->bindBuffer(
        GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
        resource->gl_pixel_buffer_id);
    context3d->bufferData(
        GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
        4 * resource->size.GetArea(),
        NULL,
        GL_DYNAMIC_DRAW);
    context3d->bindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0);
  }

  if (resource->pixels) {
    if (resource->pixel_buffer)
      return;

    resource->pixel_buffer = new uint8_t[4 * resource->size.GetArea()];
  }
}

void ResourceProvider::ReleasePixelBuffer(ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;
  DCHECK(!resource->external);
  DCHECK(!resource->exported);
  DCHECK(!resource->image_id);

  // The pixel buffer can be released while there is a pending "set pixels"
  // if completion has been forced. Any shared memory associated with this
  // pixel buffer will not be freed until the waitAsyncTexImage2DCHROMIUM
  // command has been processed on the service side. It is also safe to
  // reuse any query id associated with this resource before they complete
  // as each new query has a unique submit count.
  if (resource->pending_set_pixels) {
    DCHECK(resource->set_pixels_completion_forced);
    resource->pending_set_pixels = false;
    UnlockForWrite(id);
  }

  if (resource->type == GLTexture) {
    if (!resource->gl_pixel_buffer_id)
      return;
    WebGraphicsContext3D* context3d = output_surface_->context3d();
    DCHECK(context3d);
    context3d->bindBuffer(
        GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
        resource->gl_pixel_buffer_id);
    context3d->bufferData(
        GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
        0,
        NULL,
        GL_DYNAMIC_DRAW);
    context3d->bindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0);
  }

  if (resource->pixels) {
    if (!resource->pixel_buffer)
      return;
    delete[] resource->pixel_buffer;
    resource->pixel_buffer = NULL;
  }
}

uint8_t* ResourceProvider::MapPixelBuffer(ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;
  DCHECK(!resource->external);
  DCHECK(!resource->exported);
  DCHECK(!resource->image_id);

  if (resource->type == GLTexture) {
    WebGraphicsContext3D* context3d = output_surface_->context3d();
    DCHECK(context3d);
    DCHECK(resource->gl_pixel_buffer_id);
    context3d->bindBuffer(
        GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
        resource->gl_pixel_buffer_id);
    uint8_t* image = static_cast<uint8_t*>(
        context3d->mapBufferCHROMIUM(
            GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, GL_WRITE_ONLY));
    context3d->bindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0);
    // Buffer is required to be 4-byte aligned.
    CHECK(!(reinterpret_cast<intptr_t>(image) & 3));
    return image;
  }

  if (resource->pixels)
    return resource->pixel_buffer;

  return NULL;
}

void ResourceProvider::UnmapPixelBuffer(ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;
  DCHECK(!resource->external);
  DCHECK(!resource->exported);
  DCHECK(!resource->image_id);

  if (resource->type == GLTexture) {
    WebGraphicsContext3D* context3d = output_surface_->context3d();
    DCHECK(context3d);
    DCHECK(resource->gl_pixel_buffer_id);
    context3d->bindBuffer(
        GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
        resource->gl_pixel_buffer_id);
    context3d->unmapBufferCHROMIUM(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM);
    context3d->bindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0);
  }
}

void ResourceProvider::SetPixelsFromBuffer(ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;
  DCHECK(!resource->locked_for_write);
  DCHECK(!resource->lock_for_read_count);
  DCHECK(!resource->external);
  DCHECK(!resource->exported);
  DCHECK(ReadLockFenceHasPassed(resource));
  DCHECK(!resource->image_id);
  LazyAllocate(resource);

  if (resource->gl_id) {
    WebGraphicsContext3D* context3d = output_surface_->context3d();
    DCHECK(context3d);
    DCHECK(resource->gl_pixel_buffer_id);
    context3d->bindTexture(GL_TEXTURE_2D, resource->gl_id);
    context3d->bindBuffer(
        GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
        resource->gl_pixel_buffer_id);
    context3d->texSubImage2D(GL_TEXTURE_2D,
                             0, /* level */
                             0, /* x */
                             0, /* y */
                             resource->size.width(),
                             resource->size.height(),
                             resource->format,
                             GL_UNSIGNED_BYTE,
                             NULL);
    context3d->bindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0);
    context3d->deleteBuffer(resource->gl_pixel_buffer_id);
    resource->gl_pixel_buffer_id = 0;
  }

  if (resource->pixels) {
    DCHECK(!resource->mailbox.IsValid());
    DCHECK(resource->pixel_buffer);
    DCHECK(resource->format == GL_RGBA);

    ScopedWriteLockSoftware lock(this, id);
    std::swap(resource->pixels, resource->pixel_buffer);
    delete[] resource->pixel_buffer;
    resource->pixel_buffer = NULL;
  }
}

void ResourceProvider::BindForSampling(ResourceProvider::ResourceId resource_id,
                                       GLenum target, GLenum filter) {
  DCHECK(thread_checker_.CalledOnValidThread());
  WebGraphicsContext3D* context3d = output_surface_->context3d();
  ResourceMap::iterator it = resources_.find(resource_id);
  DCHECK(it != resources_.end());
  Resource* resource = &it->second;
  DCHECK(resource->lock_for_read_count);
  DCHECK(!resource->locked_for_write || resource->set_pixels_completion_forced);

  GLC(context3d, context3d->bindTexture(target, resource->gl_id));
  if (filter != resource->filter) {
    GLC(context3d, context3d->texParameteri(target,
                                            GL_TEXTURE_MIN_FILTER,
                                            filter));
    GLC(context3d, context3d->texParameteri(target,
                                            GL_TEXTURE_MAG_FILTER,
                                            filter));
    resource->filter = filter;
  }
}

void ResourceProvider::BeginSetPixels(ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;
  DCHECK(!resource->pending_set_pixels);

  LazyCreate(resource);
  DCHECK(resource->gl_id || resource->allocated);
  DCHECK(ReadLockFenceHasPassed(resource));
  DCHECK(!resource->image_id);

  bool allocate = !resource->allocated;
  resource->allocated = true;
  LockForWrite(id);

  if (resource->gl_id) {
    WebGraphicsContext3D* context3d = output_surface_->context3d();
    DCHECK(context3d);
    DCHECK(resource->gl_pixel_buffer_id);
    context3d->bindTexture(GL_TEXTURE_2D, resource->gl_id);
    context3d->bindBuffer(
        GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
        resource->gl_pixel_buffer_id);
    if (!resource->gl_upload_query_id)
      resource->gl_upload_query_id = context3d->createQueryEXT();
    context3d->beginQueryEXT(
        GL_ASYNC_PIXEL_TRANSFERS_COMPLETED_CHROMIUM,
        resource->gl_upload_query_id);
    if (allocate) {
      context3d->asyncTexImage2DCHROMIUM(GL_TEXTURE_2D,
                                         0, /* level */
                                         resource->format,
                                         resource->size.width(),
                                         resource->size.height(),
                                         0, /* border */
                                         resource->format,
                                         GL_UNSIGNED_BYTE,
                                         NULL);
    } else {
      context3d->asyncTexSubImage2DCHROMIUM(GL_TEXTURE_2D,
                                            0, /* level */
                                            0, /* x */
                                            0, /* y */
                                            resource->size.width(),
                                            resource->size.height(),
                                            resource->format,
                                            GL_UNSIGNED_BYTE,
                                            NULL);
    }
    context3d->endQueryEXT(GL_ASYNC_PIXEL_TRANSFERS_COMPLETED_CHROMIUM);
    context3d->bindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0);
  }

  if (resource->pixels)
    SetPixelsFromBuffer(id);

  resource->pending_set_pixels = true;
  resource->set_pixels_completion_forced = false;
}

void ResourceProvider::ForceSetPixelsToComplete(ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;
  DCHECK(resource->locked_for_write);
  DCHECK(resource->pending_set_pixels);
  DCHECK(!resource->set_pixels_completion_forced);

  if (resource->gl_id) {
    WebGraphicsContext3D* context3d = output_surface_->context3d();
    GLC(context3d, context3d->bindTexture(GL_TEXTURE_2D, resource->gl_id));
    GLC(context3d, context3d->waitAsyncTexImage2DCHROMIUM(GL_TEXTURE_2D));
    GLC(context3d, context3d->bindTexture(GL_TEXTURE_2D, 0));
  }

  resource->set_pixels_completion_forced = true;
}

bool ResourceProvider::DidSetPixelsComplete(ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;
  DCHECK(resource->locked_for_write);
  DCHECK(resource->pending_set_pixels);

  if (resource->gl_id) {
    WebGraphicsContext3D* context3d = output_surface_->context3d();
    DCHECK(context3d);
    DCHECK(resource->gl_upload_query_id);
    unsigned complete = 1;
    context3d->getQueryObjectuivEXT(
        resource->gl_upload_query_id,
        GL_QUERY_RESULT_AVAILABLE_EXT,
        &complete);
    if (!complete)
      return false;
  }

  resource->pending_set_pixels = false;
  UnlockForWrite(id);

  return true;
}

void ResourceProvider::CreateForTesting(ResourceId id) {
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;
  LazyCreate(resource);
}

void ResourceProvider::LazyCreate(Resource* resource) {
  if (resource->type != GLTexture || resource->gl_id != 0)
    return;

  // Early out for resources that don't require texture creation.
  if (resource->texture_pool == 0)
    return;

  WebGraphicsContext3D* context3d = output_surface_->context3d();
  DCHECK(context3d);
  // Create and set texture properties. Allocation is delayed until needed.
  resource->gl_id = CreateTextureId(context3d);
  GLC(context3d, context3d->texParameteri(GL_TEXTURE_2D,
                                          GL_TEXTURE_POOL_CHROMIUM,
                                          resource->texture_pool));
  if (use_texture_usage_hint_ && resource->hint == TextureUsageFramebuffer) {
    GLC(context3d, context3d->texParameteri(GL_TEXTURE_2D,
                                            GL_TEXTURE_USAGE_ANGLE,
                                            GL_FRAMEBUFFER_ATTACHMENT_ANGLE));
  }
}

void ResourceProvider::AllocateForTesting(ResourceId id) {
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;
  LazyAllocate(resource);
}

void ResourceProvider::LazyAllocate(Resource* resource) {
  DCHECK(resource);
  LazyCreate(resource);

  DCHECK(resource->gl_id || resource->allocated);
  if (resource->allocated || !resource->gl_id)
    return;
  resource->allocated = true;
  WebGraphicsContext3D* context3d = output_surface_->context3d();
  gfx::Size& size = resource->size;
  GLenum format = resource->format;
  GLC(context3d, context3d->bindTexture(GL_TEXTURE_2D, resource->gl_id));
  if (use_texture_storage_ext_ && IsTextureFormatSupportedForStorage(format)) {
    GLenum storage_format = TextureToStorageFormat(format);
    GLC(context3d, context3d->texStorage2DEXT(GL_TEXTURE_2D,
                                              1,
                                              storage_format,
                                              size.width(),
                                              size.height()));
  } else {
    GLC(context3d, context3d->texImage2D(GL_TEXTURE_2D,
                                         0,
                                         format,
                                         size.width(),
                                         size.height(),
                                         0,
                                         format,
                                         GL_UNSIGNED_BYTE,
                                         NULL));
  }
}

void ResourceProvider::EnableReadLockFences(ResourceProvider::ResourceId id,
                                            bool enable) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;
  resource->enable_read_lock_fences = enable;
}

void ResourceProvider::AcquireImage(ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;

  DCHECK(!resource->pixels);
  DCHECK(!resource->external);
  DCHECK(!resource->exported);

  if (resource->image_id != 0) {
    // If we had previously allocated an image for this resource,
    // release it before allocating a new one to prevent leaks.
    ReleaseImage(id);
  }

  WebGraphicsContext3D* context3d = output_surface_->context3d();
  DCHECK(context3d);
  resource->image_id = context3d->createImageCHROMIUM(
      resource->size.width(), resource->size.height(), GL_RGBA8_OES);
  DCHECK(resource->image_id);
}

void ResourceProvider::ReleaseImage(ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;

  DCHECK(!resource->external);
  DCHECK(!resource->exported);
  DCHECK(!resource->pixels);

  if (resource->image_id == 0)
    return;

  WebGraphicsContext3D* context3d = output_surface_->context3d();
  DCHECK(context3d);
  context3d->destroyImageCHROMIUM(resource->image_id);
  resource->image_id = 0;
}

uint8_t* ResourceProvider::MapImage(ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;

  DCHECK(ReadLockFenceHasPassed(resource));
  DCHECK(!resource->external);
  DCHECK(!resource->exported);
  DCHECK(!resource->pixels);
  DCHECK(resource->image_id);

  WebGraphicsContext3D* context3d = output_surface_->context3d();
  DCHECK(context3d);
  return static_cast<uint8_t*>(
      context3d->mapImageCHROMIUM(resource->image_id, GL_READ_WRITE));
}

void ResourceProvider::UnmapImage(ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;

  DCHECK(!resource->external);
  DCHECK(!resource->exported);
  DCHECK(!resource->pixels);
  DCHECK(resource->image_id);

  WebGraphicsContext3D* context3d = output_surface_->context3d();
  DCHECK(context3d);
  context3d->unmapImageCHROMIUM(resource->image_id);
}

void ResourceProvider::BindImage(ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;

  DCHECK(!resource->external);
  DCHECK(!resource->exported);
  DCHECK(!resource->pixels);
  DCHECK(resource->image_id);

  LazyCreate(resource);
  resource->allocated = true;

  WebGraphicsContext3D* context3d = output_surface_->context3d();
  DCHECK(context3d);
  context3d->bindTexture(GL_TEXTURE_2D, resource->gl_id);
  context3d->bindTexImage2DCHROMIUM(GL_TEXTURE_2D, resource->image_id);
}

int ResourceProvider::GetImageStride(ResourceId id) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ResourceMap::iterator it = resources_.find(id);
  CHECK(it != resources_.end());
  Resource* resource = &it->second;

  DCHECK(!resource->external);
  DCHECK(!resource->exported);
  DCHECK(!resource->pixels);
  DCHECK(resource->image_id);

  WebGraphicsContext3D* context3d = output_surface_->context3d();
  DCHECK(context3d);
  int stride = 0;
  context3d->getImageParameterivCHROMIUM(
      resource->image_id, GL_IMAGE_ROWBYTES_CHROMIUM, &stride);
  return stride;
}

}  // namespace cc


text:
use std::collections::{BTreeMap, BTreeSet};
use std::env;
use std::env::{current_exe};
use std::io::{self, BufRead, BufReader, ErrorKind, Write};
use std::fs::{read_link, OpenOptions};
use std::fs::File;
use std::os::unix::fs::{symlink};
use std::path::{Path, PathBuf};

use libc::pid_t;
use libmount::{BindMount, Tmpfs, Remount};

use crate::config::{Container, Settings};
use crate::config::command::{CommandInfo, CapsuleInfo};
use crate::config::read_settings::{MergedSettings};
use crate::config::volumes::Volume;
use crate::container::root::{change_root};
use crate::container::mount::{mount_dev, mount_proc, mount_run,  mount_system_dirs, unmount};
use crate::container::network::detect_local_dns;
use crate::container::util::{clean_dir, hardlink_dir, version_from_symlink};
use crate::file_util::{Dir, safe_ensure_dir};
use crate::process_util::{DEFAULT_PATH, PROXY_ENV_VARS};
use crate::storage_dir::sanitize;
use crate::wrapper::snapshot::make_snapshot;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WriteMode {
    ReadOnly,
    TransientHardlinkCopy(pid_t),
}

fn create_storage_dir(storage_dir: &Path, project_root: &Path)
    -> Result<PathBuf, String>
{
    let name = match project_root.file_name().and_then(|x| x.to_str()) {
        Some(name) => name,
        None => return Err(format!(
            "Project dir {:?} is either root or has bad characters",
            project_root)),
    };
    let path = storage_dir.join(name);
    if !path.exists() {
        return Ok(path);
    }
    for i in 1..101 {
        let result = format!("{}-{}", name, i);
        let path = storage_dir.join(result);
        if !path.exists() {
            return Ok(path);
        }
    }
    return Err(format!("Too many similar paths named {} in {:?}",
        name, storage_dir));
}

fn make_cache_dir(_project_root: &Path, vagga_base: &Path,
    settings: &MergedSettings)
    -> Result<PathBuf, String>
{
    match settings.cache_dir {
        Some(ref dir) if settings.shared_cache => {
            if !dir.exists() {
                return Err(format!(concat!("Cache directory {:?} must exists.",
                    " Please either create it or remove that configuration",
                    " setting"), dir));
            }
            return Ok(dir.clone());
        }
        _ => {
            let dir = vagga_base.join(".cache");
            safe_ensure_dir(&dir)?;
            return Ok(dir);
        }
   }
}

fn _vagga_base(project_root: &Path, settings: &MergedSettings)
    -> Result<Result<PathBuf, (PathBuf, PathBuf)>, String>
{
    if let Some(ref dir) = settings.storage_dir {
        let lnkdir = project_root.join(".vagga/.lnk");
        match read_link(&lnkdir) {
            Ok(lnk) => {
                if let Some(name) = lnk.file_name() {
                    let target = dir.join(name);
                    if lnk.parent() != Some(&*dir) {
                        let newlnk = dir.join(name);
                        if newlnk.exists() {
                            return Err(format!("You have set `storage-dir` \
                                to {:?}, but .vagga/.lnk points to {:?}. \
                                You probably need to run \
                                `ln -sfn {:?} .vagga/.lnk`",
                                dir, lnk, newlnk));
                        } else {
                            return Err(format!("You have set `storage-dir` \
                                to {:?}, but .vagga/.lnk points to {:?}. \
                                If storage dir is new, \
                                just remove `.vagga` dir.",
                                dir, lnk));
                        }
                    }
                    if !lnkdir.exists() {
                        return Err(format!("Your .vagga/.lnk points to a \
                            non-existent directory. Presumably you deleted \
                            dir {:?}. Just remove .vagga/.lnk now.",
                            lnk));
                    }
                    return Ok(Ok(target));
                } else {
                    return Err(format!(concat!("Bad link .vagga/.lnk: {:?}.",
                        " You are pobably need to remove it now"),
                        lnk));
                }
            }
            Err(ref e) if e.kind() == ErrorKind::NotFound => {
                return Ok(Err((lnkdir, dir.clone())));
            }
            Err(ref e) => {
                return Err(format!("Can't read link .vagga/.lnk: {}", e));
            }
        };
    } else {
        return Ok(Ok(project_root.join(".vagga")));
    }
}

pub fn get_vagga_base(project_root: &Path, settings: &MergedSettings)
    -> Result<Option<PathBuf>, String>
{
    return _vagga_base(project_root, settings).map(|x| x.ok());
}

fn vagga_base(project_root: &Path, settings: &MergedSettings)
    -> Result<PathBuf, String>
{
    if env_is_set("_VAGGA_IN_CAPSULE") {
        return Ok("/vagga/base".into());
    }
    match _vagga_base(project_root, settings) {
        Ok(Err((lnkdir, dir))) => {
            let ref svar = settings.storage_subdir_from_env_var;
            let target = if let Some(ref var) = *svar {
                if let Ok(user_dir) = env::var("_VAGGA_STORAGE_SUBDIR") {
                    let sanitized = sanitize(&user_dir);
                    if sanitized.len() == 0 {
                        return Err(format!("Couldn't determine storage-dir \
                            subdir from environment, original value {:?} \
                            (got from {:?})",
                            user_dir, var));
                    } else {
                        info!("Storage dir {:?} (got from {:?})",
                            user_dir, var);
                        dir.join(user_dir)
                    }
                } else {
                    return Err("Internal error: _VAGGA_STORAGE_SUBDIR".into())
                }
            } else {
                create_storage_dir(&dir, project_root)?
            };
            safe_ensure_dir(&target)?;
            symlink(&target, &lnkdir)
                .map_err(|e| format!("Error symlinking storage: {}", e))?;
            if svar.is_none() {
                // Backlink is useful only when not using from-env-var
                symlink(project_root, &target.join(".lnk"))
                    .map_err(|e| format!("Error symlinking storage: {}", e))?;
            }
            return Ok(target)
        }
        Ok(Ok(path)) => {
            return Ok(path);
        }
        Err(e) => {
            return Err(e);
        }
    }
}

fn make_mountpoint(project_root: &Path) -> Result<(), String> {
    let vagga_dir = project_root.join(".vagga");
    safe_ensure_dir(&vagga_dir)?;
    let mnt_dir = vagga_dir.join(".mnt");
    safe_ensure_dir(&mnt_dir)?;
    return Ok(());
}

fn env_is_set(key: &str) -> bool {
    env::var(key).map(|x| x.len() > 0).unwrap_or(false)
}

pub fn setup_base_filesystem(project_root: &Path, settings: &MergedSettings)
    -> Result<(), String>
{
    let mnt_dir = project_root.join(".vagga/.mnt");
    make_mountpoint(project_root)?;
    Tmpfs::new(&mnt_dir).size_bytes(100 << 20).mount()
         .map_err(|e| format!("{}", e))?;

    let proc_dir = mnt_dir.join("proc");
    try_msg!(Dir::new(&proc_dir).create(),
             "Error creating /proc: {err}");
    mount_proc(&proc_dir)?;

    let dev_dir = mnt_dir.join("dev");
    try_msg!(Dir::new(&dev_dir).create(),
             "Error creating /dev: {err}");
    mount_dev(&dev_dir)?;

    let sys_dir = mnt_dir.join("sys");
    try_msg!(Dir::new(&sys_dir).create(),
             "Error creating /sys: {err}");
    try_msg!(BindMount::new("/sys", &sys_dir).mount(), "mount /sys: {err}");
    let selinux = sys_dir.join("fs/selinux");
    if selinux.is_dir() {
        // Need this go get some selinux-aware commands to work (see #65)
        Remount::new(&sys_dir.join("fs/selinux"))
            .bind(true).readonly(true).remount()
            .map_err(|e| {
                let err = e.to_string();
                // Sometimes dir exists but is not a mountpoint, that's okay
                if !err.contains("Cannot find mount point") {
                    warn!("remount /sys/fs/selinux: {}", e)
                }
            }).ok();
    }

    let vagga_dir = mnt_dir.join("vagga");
    try_msg!(Dir::new(&vagga_dir).create(),
             "Error creating /vagga: {err}");

    let bin_dir = vagga_dir.join("bin");
    try_msg!(Dir::new(&bin_dir).create(),
             "Error creating /vagga/bin: {err}");
    try_msg!(BindMount::new(&current_exe().unwrap().parent().unwrap(),
                            &bin_dir)
             .readonly(true)
             .mount(), "mount /vagga/bin: {err}");

    if let Some(ref storage_dir) = settings.storage_dir {
        let tgtstorage = vagga_dir.join("storage");
        try_msg!(Dir::new(&tgtstorage).create(),
            "Error creating {path:?} directory: {err}", path=&tgtstorage);
        try_msg!(BindMount::new(storage_dir, &tgtstorage).mount(),
            "mount storage: {err}");
    }

    let etc_dir = mnt_dir.join("etc");
    try_msg!(Dir::new(&etc_dir).create(),
             "Error creating /etc: {err}");
    let ref hosts_path = etc_dir.join("hosts");
    File::create(hosts_path).map_err(|e|
        format!("Cannot create {:?} file: {}", hosts_path, e))?;
    BindMount::new(Path::new("/etc/hosts"), hosts_path)
        .mount()
        .map_err(|e| format!("Error mounting /etc/hosts: {}", e))?;

    let local_dns = if env_is_set("VAGGA_IN_NAMESPACE") ||
                       env_is_set("VAGGA_NAMESPACE_DIR")
    {
        detect_local_dns().map_err(|e| format!("{}", e))?
    } else {
        None
    };
    let resolv_source = if let Some(_) = local_dns {
        // TODO(tailhook) do it on detached tmpfs
        File::create("/tmp/resolv.conf")
            .and_then(|mut f|
                // redirect to a gateway IP
                writeln!(&mut f, "nameserver 172.23.255.1"))
            .map_err(|e| format!("Can't create temporary resolv.conf: {}", e))?;
        Path::new("/tmp/resolv.conf")
    } else {
        Path::new("/etc/resolv.conf")
    };
    let ref resolv_path = etc_dir.join("resolv.conf");
    if !resolv_path.exists() {
        File::create(resolv_path).map_err(|e|
            format!("Cannot create {:?} file: {}", resolv_path, e))?;
    }
    BindMount::new(&resolv_source, resolv_path)
        .mount()
        .map_err(|e| format!("Error copying /etc/resolv.conf: {}", e))?;

    let local_base = vagga_dir.join("base");
    safe_ensure_dir(&local_base)?;
    let vagga_base = vagga_base(project_root, settings)?;

    try_msg!(BindMount::new(&vagga_base, &local_base).mount(),
        "mount /vagga/base: {err}");
    safe_ensure_dir(&local_base.join(".roots"))?;
    safe_ensure_dir(&local_base.join(".transient"))?;

    let cache_dir = vagga_dir.join("cache");
    try_msg!(Dir::new(&cache_dir).create(),
        "Error creating /vagga/cache: {err}");
    let locl_cache = make_cache_dir(project_root, &vagga_base, settings)?;
    try_msg!(BindMount::new(&locl_cache, &cache_dir).mount(),
        "mount /vagga/cache: {err}");

    if let Ok(nsdir) = env::var("VAGGA_NAMESPACE_DIR") {
        let newns_dir = vagga_dir.join("namespaces");
        try_msg!(Dir::new(&newns_dir).recursive(true).create(),
            "Error creating directory for namespaces: {err}");
        try_msg!(BindMount::new(&nsdir, &newns_dir).mount(),
             "namespace dir: {err}");
    }

    let volume_dir = mnt_dir.join("volumes");
    try_msg!(Dir::new(&volume_dir).create(),
        "Error creating /volumes: {err}");
    for (name, source_path) in &settings.external_volumes {
        let dest = volume_dir.join(name);
        if source_path.is_dir() {
            try_msg!(Dir::new(&dest).create(),
                "Error creating {p:?}: {err}", p=dest);
        } else {
            try_msg!(File::create(&dest),
                "Error creating {p:?}: {err}", p=dest);
        }
        try_msg!(BindMount::new(source_path, &dest).mount(),
            "volume: {err}");
    }

    let work_dir = mnt_dir.join("work");
    try_msg!(Dir::new(&work_dir).create(),
        "Error creating /work: {err}");
    try_msg!(BindMount::new(project_root, &work_dir).mount(),
        "mount /work: {err}");

    let old_root = vagga_dir.join("old_root");
    try_msg!(Dir::new(&old_root).create(),
             "Error creating /vagga/old_root: {err}");
    change_root(&mnt_dir, &old_root)?;
    unmount(&Path::new("/work/.vagga/.mnt"))
         .map_err(|e| format!("Error unmounting `.vagga/.mnt`: {}", e))?;
    unmount(&Path::new("/vagga/old_root"))?;

    Ok(())
}

pub fn get_environment(settings: &Settings, container: &Container,
    command: Option<&CommandInfo>)
    -> Result<BTreeMap<String, String>, String>
{
    _get_environment(DEFAULT_PATH, settings, Some(container),
        command.map(|c| &c.environ))
}

pub fn get_capsule_environment(settings: &Settings,
    command: &CapsuleInfo)
    -> Result<BTreeMap<String, String>, String>
{
    _get_environment("/vagga/bin:/bin", settings, None, Some(&command.environ))
}

fn _get_environment(default_path: &str, settings: &Settings,
    container: Option<&Container>,
    command_env: Option<&BTreeMap<String, String>>)
    -> Result<BTreeMap<String, String>, String>
{
    let mut result = BTreeMap::new();
    result.insert("PATH".to_string(),
                  default_path.to_string());
    if settings.proxy_env_vars {
        for k in &PROXY_ENV_VARS {
            if let Ok(v) = env::var(k) {
                result.insert(k.to_string(), v);
            }
        }
    }
    if let Some(ref filename) = container.and_then(|x| x.environ_file.as_ref()) {
        let f = BufReader::new(
                File::open(filename)
                .map_err(|e| format!("Error reading environment file {:?}: {}",
                    filename, e))?);
        for line_read in f.lines() {
            let line = line_read
                .map_err(|e| format!("Error reading environment file {:?}: {}",
                    filename, e))?;
            let mut pair = line[..].splitn(2, '=');
            let key = pair.next().unwrap();
            let mut value = pair.next()
                .ok_or(format!("Error reading environment file {:?}: bad format",
                    filename))?;
            if value.len() > 0 && value.starts_with("\"") {
                value = value.trim_matches('"');
            }
            result.insert(key.to_string(), value.to_string());
        }
    }
    if let Some(container) = container {
        for (ref k, ref v) in container.environ.iter() {
            result.insert(k.to_string(), v.to_string());
        }
    }
    if let Some(command_env) = command_env {
        for (ref k, ref v) in command_env.iter() {
            result.insert(k.to_string(), v.to_string());
        }
    }
    for (ref k, ref v) in settings.environ.iter() {
        result.insert(k.to_string(), v.to_string());
    }
    for (k, v) in env::vars() {
        if k.starts_with("VAGGAENV_") {
            result.insert(k[9..].to_string(), v);
        } else if k.starts_with("VAGGAOPT_") || k.starts_with("VAGGACLI_") {
            result.insert(k, v);
        }
    }
    return Ok(result);
}

pub struct SetupInfo<'a> {
    pub volumes: BTreeMap<&'a PathBuf, &'a Volume>,
    pub tmp_volumes: BTreeSet<&'a String>,
    pub write_mode: WriteMode,
    pub resolv_conf_path: Option<&'a PathBuf>,
    pub hosts_file_path: Option<&'a PathBuf>,
}

impl<'a> SetupInfo<'a> {
    pub fn from_container(container: &Container) -> SetupInfo {
        let mut setup_info = SetupInfo {
            volumes: BTreeMap::new(),
            tmp_volumes: BTreeSet::new(),
            write_mode: WriteMode::ReadOnly,
            resolv_conf_path: container.resolv_conf_path.as_ref(),
            hosts_file_path: container.hosts_file_path.as_ref(),
        };
        setup_info.volumes(&container.volumes);
        setup_info
    }
    pub fn volumes(&mut self, volumes: &'a BTreeMap<PathBuf, Volume>)
        -> &mut SetupInfo<'a>
    {
        for (path, vol) in volumes.iter() {
            self.volumes.insert(&path, &vol);
        }
        self
    }
    pub fn write_mode(&mut self, write_mode: WriteMode)
        -> &mut SetupInfo<'a>
    {
        self.write_mode = write_mode;
        self
    }
}

pub fn setup_filesystem(setup_info: &SetupInfo, container_ver: &str)
    -> Result<(), String>
{
    let tgtroot = Path::new("/vagga/root");
    if !tgtroot.exists() {
        try_msg!(Dir::new(&tgtroot).create(),
                 "Can't create rootfs mountpoint: {err}");
    }
    let image_base = Path::new("/vagga/base/.roots").join(container_ver);
    match setup_info.write_mode {
        WriteMode::ReadOnly => {
            let nroot = image_base.join("root");
            try_msg!(BindMount::new(&nroot, &tgtroot).readonly(true).mount(),
                 "mount root: {err}");
        }
        WriteMode::TransientHardlinkCopy(pid) => {
            let oldpath = image_base.join("root");
            let newpath = Path::new("/vagga/base/.transient")
                .join(format!("{}.{}", container_ver, pid));
            if newpath.exists() {
                try_msg!(clean_dir(&newpath, true),
                        "Error removing dir: {err}");
            }
            try_msg!(Dir::new(&newpath).create(),
                     "Error creating directory: {err}");
            try_msg!(hardlink_dir(&oldpath, &newpath),
                "Can't hardlink {p:?}: {err}", p=newpath);
            try_msg!(BindMount::new(&newpath, &tgtroot).mount(),
                 "transient root: {err}");
        }
    };
    File::create(image_base.join("last_use"))
        .map_err(|e| warn!("Can't write image usage info: {}", e)).ok();

    mount_system_dirs()
        .map_err(|e| format!("Error mounting system dirs: {}", e))?;

    if let None = setup_info.volumes.get(&PathBuf::from("/tmp")) {
        Tmpfs::new(&tgtroot.join("tmp"))
            .size_bytes(100 << 20)
            .mode(0o1777)
            .mount().map_err(|e| format!("{}", e))?;
    }
    if let None = setup_info.volumes.get(&PathBuf::from("/run")) {
        mount_run(&tgtroot.join("run"))?;
    }

    for (path, vol) in setup_info.volumes.iter() {
        let ref rel_path = path.strip_prefix("/").unwrap();
        let dest = tgtroot.join(rel_path);

        use crate::config::volumes::Volume as V;

        match *vol {
            &V::Tmpfs(ref params) => {
                Tmpfs::new(&dest)
                    .size_bytes(params.size)
                    .mode(params.mode)
                    .mount().map_err(|e| format!("{}", e))?;
                for (subpath, info) in &params.subdirs {
                    try_msg!(Dir::new(&dest.join(&subpath))
                            .mode(info.mode)
                            .create(),
                        "Error creating subdir {sub:?} of {vol:?}: {err}",
                        sub=subpath, vol=path);
                }
                for (subpath, text) in &params.files {
                    let text = text.as_ref().map(|x| &x[..]).unwrap_or("");
                    try_msg!(File::create(&dest.join(&subpath))
                        .and_then(|mut f| f.write_all(text.as_bytes())),
                        "Error creating file {sub:?} of {vol:?}: {err}",
                        sub=subpath, vol=path);
                }
            }
            &V::VaggaBin => {
                try_msg!(BindMount::new("/vagga/bin", &dest).mount(),
                    "mount !VaggaBin: {err}");
            }
            &V::BindRW(ref bindpath) => {
                try_msg!(BindMount::new(&bindpath, &dest).mount(),
                    "mount !BindRW: {err}");
            }
            &V::BindRO(ref bindpath) => {
                try_msg!(BindMount::new(&bindpath, &dest)
                        .readonly(true)
                        .mount(),
                    "mount !BindRO: {err}");
            }
            &V::CacheDir(ref bindpath) => {
                // avoid mounting cache root
                if bindpath == Path::new("") {
                    return Err("mount !CacheDir: path must not be empty".into())
                }

                // bind path must be relative to cache root
                if bindpath.is_absolute() {
                    return Err(format!(
                        "mount !CacheDir {:?}: path must not be absolute",
                        &bindpath))
                }

                let source = Path::new("/vagga/cache").join(bindpath);
                if !source.exists() {
                    try_msg!(Dir::new(&source).recursive(true).create(),
                        "error creating dir for volume {p:?}: {err}", p=path);
                }
                try_msg!(BindMount::new(&source, &dest).mount(),
                    "mount !CacheDir: {err}");
            }
            &V::Empty => {
                Tmpfs::new(&dest)
                    .size_bytes(1)
                    .mode(0)
                    .mount().map_err(|e| format!("{}", e))?;
                try_msg!(Remount::new(&dest).bind(true).readonly(true).remount(),
                    "remount !Empty: {err}");
            }
            &V::Snapshot(ref info) => {
                let ref src = match info.container {
                    Some(ref src_container) => {
                        let container_ver = version_from_symlink(
                            format!("/work/.vagga/{}", src_container))?;
                        Path::new("/vagga/base/.roots")
                            .join(container_ver)
                            .join("root")
                            .join(rel_path)
                    },
                    None => dest.clone(),
                };
                make_snapshot(src, &dest, info)?;
            }
            &V::Container(ref child_cont) => {
                let container_ver = version_from_symlink(
                    format!("/work/.vagga/{}", child_cont))?;
                let target = Path::new("/vagga/base/.roots")
                    .join(container_ver).join("root");
                try_msg!(BindMount::new(&target, &dest).readonly(true).mount(),
                    "mount !Container: {err}");
            }
            &V::Persistent(ref info) => {
                if setup_info.tmp_volumes.contains(&info.name) {
                    let path = Path::new("/vagga/base/.volumes")
                        .join(&format!(".tmp.{}", info.name));
                    try_msg!(Dir::new(&path).recursive(true).create(),
                        "error creating dir for volume {p:?}: {err}", p=path);
                    try_msg!(BindMount::new(&path, &dest).mount(),
                        "mount !BindRW: {err}");
                } else {
                    let path = Path::new("/vagga/base/.volumes")
                        .join(&info.name);
                    if info.init_command.is_none() {
                        try_msg!(Dir::new(&path)
                                .recursive(true)
                                .uid(info.owner_uid)
                                .gid(info.owner_gid)
                                .create(),
                            "error creating dir for volume {p:?}: {err}",
                            p=path);
                    } else if !path.exists() {
                        return Err(format!("Internal error: \
                            Got uninitialized volume {:?}",
                            info.name));
                    }
                    try_msg!(BindMount::new(&path, &dest).mount(),
                        "mount !BindRW: {err}");
                };
            }
        }
    }
    if let Ok(_) = env::var("VAGGA_NAMESPACE_DIR") {
        let newns_dir = tgtroot.join("tmp/vagga/namespaces");
        try_msg!(Dir::new(&newns_dir).recursive(true).create(),
            "Error creating directory for namespaces: {err}");
        try_msg!(BindMount::new("/vagga/namespaces", &newns_dir).mount(),
            "mount namespaces: {err}");
    }

    if let Some(path) = setup_info.resolv_conf_path {
        if !setup_info.volumes.contains_key(&path) {
            let ref path = tgtroot.join(path.strip_prefix("/").unwrap());
            create_and_mount(Path::new("/etc/resolv.conf"), path)?;
        }
    }
    if let Some(path) = setup_info.hosts_file_path {
        if !setup_info.volumes.contains_key(&path) {
            let ref path = tgtroot.join(path.strip_prefix("/").unwrap());
            create_and_mount(Path::new("/etc/hosts"), path)?;
        }
    }

    change_root(&tgtroot, &tgtroot.join("tmp"))
         .map_err(|e| format!("Error changing root: {}", e))?;
    unmount(&Path::new("/tmp"))
         .map_err(|e| format!("Error unmounting `/tmp`: {}", e))?;
    Ok(())
}

fn create_and_mount(src: &Path, dst: &Path) -> Result<(), String> {
    OpenOptions::new().write(true).create_new(true).open(dst)
        .map(|_| ())
        .or_else(|e| {
            if e.kind() == io::ErrorKind::AlreadyExists {
                Ok(())
            } else {
                Err(format!("Cannot create file {:?}: {}", dst, e))
            }
        })?;
    BindMount::new(src, dst)
        .mount()
        .map_err(|e| format!("Error mounting {:?}: {}", src, e))?;
    Ok(())
}

redpajama_set_name: RedPajamaGithub


text:
from . import util
from .Sina.Sina import Sina

from datetime import datetime
from .connection import *
import time
import websockets
import json
import asyncio
import threading
import re
import traceback

class NotLoginError(Exception):
    def __init__(self):
        super().__init__()

class SinaL2:

    def __init__(
        self,
        username=None,
        pwd=None,
        symbols=None,
        hq='hq_pjb',
        query=['quotation', 'transaction', "orders"],
        on_recv_data=None,   # 收到数据以后的回调函数
        use_logger=True,
        **kwargs
    ):
        super().__init__(**kwargs)
        self.on_recv_data = on_recv_data  # 回调函数
        self.ip = util.get_client_ip()
        self.hq = hq
        self.query = query

        if use_logger:
            self.logger = util.get_logger(self.__class__.__name__)

        self.sina = Sina(login=True)
        self.is_login = False
        self.stopped = False
        self.terminated = False

        if symbols is None:
            self.symbols = self.sina.get_symbols()
        else:
            self.symbols = symbols
        self.websockets = dict()

    def login(self, verify=False):
        return self.sina.login(verify=verify)

    @asyncio.coroutine
    def get_ws_token(self, qlist):
        req = self.sina.session.get(
            URL_WSKT_TOKEN,
            params=PARAM_WSKT_TOKEN(
                ip=self.ip,
                qlist=qlist,
                hq=self.hq
            ),
            headers=HEADERS_WSKT_TOKEN(),
            timeout=5
        )
        response = re.findall(r'(\{.*\})', req.text)[0]
        response = json.loads(
            response
            .replace(',', ',"')
            .replace('{', '{"')
            .replace(':', '":')
        )
        return response

    # 2cn_是3秒一条的Level2 10档行情
    # 2cn_symbol_0,2cn_symbol_1是逐笔数据
    # 2cn_symbol_orders是挂单数据
    def generate_qlist(self, qlist, symbol):
        if 'quotation' in self.query:
            if qlist != '':
                qlist += ','
            qlist += "2cn_%s" % (symbol)
        if 'orders' in self.query:
            if qlist != '':
                qlist += ','
            qlist += "2cn_%s_orders" % (symbol)
        if 'transaction' in self.query:
            if qlist != '':
                qlist += ','
            qlist += "2cn_%s_0,2cn_%s_1" % (symbol, symbol)
        if 'info' in self.query:
            if qlist != '':
                qlist += ','
            qlist += "%s_i" % (symbol)
        return qlist

    @asyncio.coroutine
    def create_ws(self, qlist, symbol_list):
        retry = True
        while retry:
            try:
                response = yield from self.get_ws_token(qlist)
                if response["msg_code"] == 1:
                    token = response["result"]
                    self.logger.info(
                        "成功获取到token, symbol_list = {}".format(symbol_list))
                    retry = False
                else:
                    self.logger.warning("{},{}".format(response, qlist))
                    if response["msg_code"] == -1 or \
                                    response["msg_code"] == -11:
                        time.sleep(2)
                        self.logger.warning("尝试重新登录新浪")
                        self.sina.login(verify=False)

            except Exception as e:
                self.logger.warning(e)

        url_wss = 'wss://ff.sinajs.cn/wskt?token=' + token + '&list=' + qlist

        while True:  # 建立websocket连接
            try:
                ws = yield from websockets.connect(url_wss)
                self.websockets[symbol_list[0]] = dict()
                self.websockets[symbol_list[0]]["ws"] = ws
                self.websockets[symbol_list[0]]["qlist"] = qlist
                self.websockets[symbol_list[0]]["token"] = token
                self.websockets[symbol_list[0]]["renewed"] = datetime.now()
                self.websockets[symbol_list[0]]["trial_times"] = 0
                self.logger.info(
                    "成功建立ws连接, {}, symbol_list = {}".format(
                        threading.current_thread().name,
                        symbol_list
                    )
                )
                break
            except Exception as e:
                self.logger.warning(
                    "重试 websockets.connect , {}, symbol_list = {}"
                    .format(
                        threading.current_thread().name,
                        symbol_list
                    )
                )

        while not self.stopped:
            try:
                message = yield from ws.recv()
                if self.on_recv_data is None:
                    print(message)
                else:
                    self.on_recv_data(message)

            except Exception as e:
                traceback.print_exc()
                self.logger.error(
                    "{},{}"
                    .format(
                        e,
                        threading.current_thread().name,
                    )
                )
                ws.close()
                if not self.stopped:
                    yield from self.create_ws(qlist=qlist, symbol_list=symbol_list)

    @asyncio.coroutine
    def renew_token(self, symbol):
        try:
            response = yield from self.get_ws_token(
                self.websockets[symbol]["qlist"]
            )
            if response["msg_code"] == 1:
                token = response["result"]
                self.websockets[symbol]["token"] = token
                self.websockets[symbol]["renewed"] = datetime.now()
                yield from self.websockets[symbol]["ws"].send("*" + token)
                self.websockets[symbol]["trial_times"] = 0
            else:
                self.websockets[symbol]["trial_times"] += 1
                self.logger.info(response["result"])
        except Exception as e:
            self.websockets[symbol]["trial_times"] += 1
            self.logger.warning("token获取失败第{}次,待会儿重试".format(
                self.websockets[symbol]["trial_times"]))

    def websocket_creator(self):
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        # 首先从新浪获取股票列表
        symbol_list = self.symbols
        # Cut symbol_list
        weight = (len(self.query) +
                  1) if ('transaction' in self.query) else len(self.query)
        step = int(64 / weight)
        symbol_list_slice = [
            symbol_list[i: i + step]
            for i in range(0, len(symbol_list), step)
        ]

        tasks = list()
        for symbol_list in symbol_list_slice:
            qlist = ''
            for symbol in symbol_list:
                qlist = self.generate_qlist(qlist=qlist, symbol=symbol)
            qlist = qlist.lower()
            tasks.append(self.create_ws(qlist, symbol_list=symbol_list))

        loop.run_until_complete(asyncio.wait(tasks))
        loop.close()

    # 用于定时发送空字符串
    def token_sender(self):
        while not self.stopped:
            self.logger.info("开启话唠模式每55秒的定时与服务器聊天")
            start = datetime.now()
            tasks = list()
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)

            for symbol in self.websockets.keys():
                ws = self.websockets[symbol]["ws"]
                if ws.open:
                    tasks.append(
                        ws.send("*" + self.websockets[symbol]["token"]))

            if len(tasks) > 0:
                loop.run_until_complete(asyncio.wait(tasks))
                loop.close()
            self.logger.info(
                "消息全部发送完毕. 耗时:%s" %
                (datetime.now() - start).total_seconds()
            )
            time.sleep(55)

    # 持续检查一次更新token
    def token_renewer(self):
        while not self.stopped:
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            tasks = list()
            for symbol in self.websockets.keys():
                ws = self.websockets[symbol]["ws"]
                if ws.open:
                    if (
                        datetime.now() - self.websockets[symbol]["renewed"]
                    ).total_seconds() > 180:
                        tasks.append(self.renew_token(symbol))

            if len(tasks) > 0:
                loop.run_until_complete(asyncio.wait(tasks))
                loop.close()
            time.sleep(1)

    def stop(self):
        """
        调用stop()以后实际上并不会立刻停止,
        一般会由ws断开(会在60秒内发生)以后退出循环,
        伴随start()中三个线程的全部结束而停止
        :return:
        """
        self.stopped = True

    def start(self):
        """
        """
        self.is_login = self.login()

        if not self.is_login:
            raise NotLoginError
        self.stopped = False
        self.terminated = False
        # 开启token manager
        tokenRenewer = threading.Thread(target=self.token_renewer)
        tokenSender = threading.Thread(target=self.token_sender)

        websocketCreator = threading.Thread(target=self.websocket_creator)

        tokenRenewer.start()		# 用于更新token
        tokenSender.start()			# 用于定时发送token
        websocketCreator.start()  # 用于建立websocket并接收消息

        tokenRenewer.join()
        tokenSender.join()
        websocketCreator.join()

        self.terminated = True
        self.logger.info("SinaL2结束")


text:
@interface SOCKSProxySocket()
@property (nonatomic, strong) GCDAsyncSocket *proxySocket;
@property (nonatomic, strong) GCDAsyncSocket *outgoingSocket;
@property (nonatomic) dispatch_queue_t delegateQueue;
@property (nonatomic) NSUInteger totalBytesWritten;
@property (nonatomic) NSUInteger totalBytesRead;
@end

@implementation SOCKSProxySocket

- (id) initWithSocket:(GCDAsyncSocket *)socket delegate:(id<SOCKSProxySocketDelegate>)delegate {
    if (self = [super init]) {
        _delegate = delegate;
        self.delegateQueue = dispatch_queue_create("SOCKSProxySocket socket delegate queue", 0);
        self.callbackQueue = dispatch_queue_create("SOCKSProxySocket callback queue", 0);
        self.proxySocket = socket;
        self.proxySocket.delegate = self;
        self.proxySocket.delegateQueue = self.delegateQueue;
        self.outgoingSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:self.delegateQueue];
        [self socksOpen];
    }
    return self;
}

- (void) socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    if (tag == SOCKS_OPEN) {
        //      +-----+--------+
        // NAME | VER | METHOD |
        //      +-----+--------+
        // SIZE |  1  |   1    |
        //      +-----+--------+
        //
        // Note: Size is in bytes
        //
        // Version = 5 (for SOCKS5)
        // Method  = 0 (No authentication, anonymous access)
        NSUInteger responseLength = 2;
        uint8_t *responseBytes = malloc(responseLength * sizeof(uint8_t));
        responseBytes[0] = 5; // VER = SOCKS5
        responseBytes[1] = 0; // METHOD = No Auth
        NSData *responseData = [NSData dataWithBytesNoCopy:responseBytes length:responseLength freeWhenDone:YES];
        [sock writeData:responseData withTimeout:-1 tag:SOCKS_OPEN];
        [sock readDataToLength:4 withTimeout:TIMEOUT_CONNECT tag:SOCKS_CONNECT_INIT];
    } else if (tag == SOCKS_CONNECT_INIT) {
        //      +-----+-----+-----+------+------+------+
        // NAME | VER | CMD | RSV | ATYP | ADDR | PORT |
        //      +-----+-----+-----+------+------+------+
        // SIZE |  1  |  1  |  1  |  1   | var  |  2   |
        //      +-----+-----+-----+------+------+------+
        //
        // Note: Size is in bytes
        //
        // Version      = 5 (for SOCKS5)
        // Command      = 1 (for Connect)
        // Reserved     = 0
        // Address Type = 3 (1=IPv4, 3=DomainName 4=IPv6)
        // Address      = P:D (P=LengthOfDomain D=DomainWithoutNullTermination)
        // Port         = 0
        uint8_t *requestBytes = (uint8_t*)[data bytes];
        uint8_t addressType = requestBytes[3];
        if (addressType == 1) {
            [sock readDataToLength:4 withTimeout:-1 tag:SOCKS_CONNECT_IPv4];
        } else if (addressType == 3) {
            [sock readDataToLength:1 withTimeout:TIMEOUT_CONNECT tag:SOCKS_CONNECT_DOMAIN_LENGTH];
        } else if (addressType == 4) {
            [sock readDataToLength:16 withTimeout:-1 tag:SOCKS_CONNECT_IPv6];
        }
    } else if (tag == SOCKS_CONNECT_IPv4) {
        uint8_t *address = malloc(INET_ADDRSTRLEN * sizeof(uint8_t));
        inet_ntop(AF_INET, data.bytes, (char*) address, INET_ADDRSTRLEN);
        _destinationHost = [[NSString alloc] initWithBytesNoCopy:address length:INET_ADDRSTRLEN encoding:NSUTF8StringEncoding freeWhenDone:YES];
        [sock readDataToLength:2 withTimeout:TIMEOUT_CONNECT tag:SOCKS_CONNECT_PORT];
    } else if (tag == SOCKS_CONNECT_IPv6) {
        uint8_t *address = malloc(INET6_ADDRSTRLEN * sizeof(uint8_t));
        inet_ntop(AF_INET6, data.bytes, (char*) address, INET6_ADDRSTRLEN);
        _destinationHost = [[NSString alloc] initWithBytesNoCopy:address length:INET6_ADDRSTRLEN encoding:NSUTF8StringEncoding freeWhenDone:YES];
        [sock readDataToLength:2 withTimeout:TIMEOUT_CONNECT tag:SOCKS_CONNECT_PORT];
    } else if (tag == SOCKS_CONNECT_DOMAIN) {
        _destinationHost = [[NSString alloc] initWithBytes:data.bytes length:data.length encoding:NSUTF8StringEncoding];
        [sock readDataToLength:2 withTimeout:TIMEOUT_CONNECT tag:SOCKS_CONNECT_PORT];
    } else if (tag == SOCKS_CONNECT_DOMAIN_LENGTH) {
        uint8_t *bytes = (uint8_t*)data.bytes;
        uint8_t addressLength = bytes[0];
        [sock readDataToLength:addressLength withTimeout:TIMEOUT_CONNECT tag:SOCKS_CONNECT_DOMAIN];
    } else if (tag == SOCKS_CONNECT_PORT) {
        uint16_t rawPort;
        memcpy(&rawPort, [data bytes], 2);
        _destinationPort = NSSwapBigShortToHost(rawPort);
        NSError *error = nil;
        [self.outgoingSocket connectToHost:self.destinationHost onPort:self.destinationPort error:&error];
    } else if (tag == SOCKS_INCOMING_READ) {
        [self.outgoingSocket writeData:data withTimeout:-1 tag:SOCKS_OUTGOING_WRITE];
        [self.outgoingSocket readDataWithTimeout:-1 tag:SOCKS_OUTGOING_READ];
        [self.proxySocket readDataWithTimeout:-1 tag:SOCKS_INCOMING_READ];
        NSUInteger dataLength = data.length;
        self.totalBytesWritten += dataLength;
        if (self.delegate && [self.delegate respondsToSelector:@selector(proxySocket:didWriteDataOfLength:)]) {
            dispatch_async(self.callbackQueue, ^{
                [self.delegate proxySocket:self didWriteDataOfLength:dataLength];
            });
        }
    } else if (tag == SOCKS_OUTGOING_READ) {
        [self.proxySocket writeData:data withTimeout:-1 tag:SOCKS_INCOMING_WRITE];
        [self.proxySocket readDataWithTimeout:-1 tag:SOCKS_INCOMING_READ];
        [self.outgoingSocket readDataWithTimeout:-1 tag:SOCKS_OUTGOING_READ];
        NSUInteger dataLength = data.length;
        self.totalBytesRead += dataLength;
        if (self.delegate && [self.delegate respondsToSelector:@selector(proxySocket:didReadDataOfLength:)]) {
            dispatch_async(self.callbackQueue, ^{
                [self.delegate proxySocket:self didReadDataOfLength:dataLength];
            });
        }
    }
}

- (void)socksOpen
{
	//      +-----+-----------+---------+
	// NAME | VER | NMETHODS  | METHODS |
	//      +-----+-----------+---------+
	// SIZE |  1  |    1      | 1 - 255 |
	//      +-----+-----------+---------+
	//
	// Note: Size is in bytes
	//
	// Version    = 5 (for SOCKS5)
	// NumMethods = 1
	// Method     = 0 (No authentication, anonymous access)
    [self.proxySocket readDataToLength:3 withTimeout:TIMEOUT_CONNECT tag:SOCKS_OPEN];
}

- (void) socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
    if (self.delegate && [self.delegate respondsToSelector:@selector(proxySocketDidDisconnect:withError:)]) {
        dispatch_async(self.callbackQueue, ^{
            [self.delegate proxySocketDidDisconnect:self withError:err];
        });
    }
}

- (void) socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
    // We write out 5 bytes which we expect to be:
    // 0: ver  = 5
    // 1: rep  = 0
    // 2: rsv  = 0
    // 3: atyp = 3
    // 4: size = size of addr field
    NSUInteger responseLength = 5 + host.length + 2;
    uint8_t *responseBytes = malloc(responseLength * sizeof(uint8_t));
    responseBytes[0] = 5;
    responseBytes[1] = 0;
    responseBytes[2] = 0;
    responseBytes[3] = 3;
    responseBytes[4] = (uint8_t)host.length;
    memcpy(responseBytes+5, [host UTF8String], host.length);
    uint16_t bigEndianPort = NSSwapHostShortToBig(port);
    NSUInteger portLength = 2;
	memcpy(responseBytes+5+host.length, &bigEndianPort, portLength);
    NSData *responseData = [NSData dataWithBytesNoCopy:responseBytes length:responseLength freeWhenDone:YES];
    [self.proxySocket writeData:responseData withTimeout:-1 tag:SOCKS_CONNECT_REPLY];
    [self.proxySocket readDataWithTimeout:-1 tag:SOCKS_INCOMING_READ];
}

@end


text:
package io.milton.dns.record;

import io.milton.dns.Name;

/**
 * DNAME Record  - maps a nonterminal alias (subtree) to a different domain
 *
 * @author Brian Wellington
 */

public class DNAMERecord extends SingleNameBase {

private static final long serialVersionUID = 2670767677200844154L;

DNAMERecord() {}

Record
getObject() {
	return new DNAMERecord();
}

/**
 * Creates a new DNAMERecord with the given data
 * @param alias The name to which the DNAME alias points
 */
public
DNAMERecord(Name name, int dclass, long ttl, Name alias) {
	super(name, Type.DNAME, dclass, ttl, alias, "alias");
}

/**
 * Gets the target of the DNAME Record
 */
public Name
getTarget() {
	return getSingleName();
}

/** Gets the alias specified by the DNAME Record */
public Name
getAlias() {
	return getSingleName();
}

}


text:
namespace Azure.ResourceManager.MachineLearningServices.Models
{
    /// <summary> Labeling dataset configuration definition. </summary>
    public partial class LabelingDatasetConfiguration
    {
        /// <summary> Initializes a new instance of LabelingDatasetConfiguration. </summary>
        public LabelingDatasetConfiguration()
        {
        }

        /// <summary> Initializes a new instance of LabelingDatasetConfiguration. </summary>
        /// <param name="assetName"> Name of the data asset to perform labeling. </param>
        /// <param name="incrementalDatasetRefreshEnabled"> Indicates whether to enable incremental dataset refresh. </param>
        /// <param name="datasetVersion"> AML dataset version. </param>
        internal LabelingDatasetConfiguration(string assetName, bool? incrementalDatasetRefreshEnabled, string datasetVersion)
        {
            AssetName = assetName;
            IncrementalDatasetRefreshEnabled = incrementalDatasetRefreshEnabled;
            DatasetVersion = datasetVersion;
        }

        /// <summary> Name of the data asset to perform labeling. </summary>
        public string AssetName { get; set; }
        /// <summary> Indicates whether to enable incremental dataset refresh. </summary>
        public bool? IncrementalDatasetRefreshEnabled { get; set; }
        /// <summary> AML dataset version. </summary>
        public string DatasetVersion { get; set; }
    }
}


text:
// Copyright 2018 Google LLC
//
// 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.google.vr180.api.implementations;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Handler;
import android.os.Looper;
import com.google.vr180.CameraApi.CameraStatus.BatteryStatus;
import com.google.vr180.CameraApi.CameraStatus.BatteryStatus.BatteryWarningLevel;
import com.google.vr180.api.camerainterfaces.BatteryStatusProvider;
import com.google.vr180.api.camerainterfaces.StatusNotifier;

/** Implementation of BatteryStatusProvider that uses Android APIs. */
public class AndroidBatteryStatusProvider implements BatteryStatusProvider {
  private final Context context;
  private final StatusNotifier notifier;
  private final Handler mainHandler = new Handler(Looper.getMainLooper());
  private BatteryWarningLevel warningLevel = BatteryWarningLevel.OK;
  private final int shutdownPercentage;

  /**
   * @param shutdownPercentage is the point at which the camera cannot take more photos or videos.
   *     This value must be less than the low battery value.
   */
  public AndroidBatteryStatusProvider(
      Context context, StatusNotifier notifier, int shutdownPercentage) {
    this.context = context;
    this.notifier = notifier;
    this.shutdownPercentage = shutdownPercentage;
    mainHandler.post(() -> registerWarningReceiver());
  }

  @Override
  public BatteryStatus getBatteryStatus() {
    Intent batteryStatus =
        context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
    if (batteryStatus == null) {
      // This happens in robolectric tests.
      return BatteryStatus.newBuilder().build();
    }

    int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
    int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);

    int chargingState = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, 1);
    int voltage = batteryStatus.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1);
    int temperature = batteryStatus.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1);

    return BatteryStatus.newBuilder()
        .setChargingState(BatteryStatus.ChargingState.forNumber(chargingState))
        .setBatteryPercentage((int) (100 * level / (float) scale))
        .setBatteryVoltage(voltage)
        .setBatteryTemperature(temperature)
        .setWarningLevel(warningLevel)
        .build();
  }

  private void registerWarningReceiver() {
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(Intent.ACTION_BATTERY_LOW);
    intentFilter.addAction(Intent.ACTION_BATTERY_OKAY);
    context.registerReceiver(new BatteryLevelReceiver(), intentFilter);
  }

  private class BatteryLevelReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
      if (intent.getAction().equals(Intent.ACTION_BATTERY_LOW)) {
        if (getBatteryStatus().getBatteryPercentage() <= shutdownPercentage) {
          warningLevel = BatteryWarningLevel.SHUTDOWN;
        } else {
          warningLevel = BatteryWarningLevel.LOW;
        }
        notifier.notifyStatusChanged();
      } else if (intent.getAction().equals(Intent.ACTION_BATTERY_OKAY)) {
        warningLevel = BatteryWarningLevel.OK;
        notifier.notifyStatusChanged();
      }
    }
  }
}


text:
var email = '';
var displayName = '';
var userId;
var language = null;

function supportsLocalStorage() {
    try {
        return 'localStorage' in window && window.localStorage !== null;
    } catch (e) {
        console.log("localstorage is not supported");
        return false;
    }
}

function generateUniqueId() {
    function _p8() {
        return (Math.random().toString(16) + "000000000").substr(2, 8);
    }
    return _p8() + _p8() + _p8() + _p8();
}

if (supportsLocalStorage()) {
    if (!window.localStorage.jitsiMeetId) {
        window.localStorage.jitsiMeetId = generateUniqueId();
        console.log("generated id", window.localStorage.jitsiMeetId);
    }
    userId = window.localStorage.jitsiMeetId || '';
    email = window.localStorage.email || '';
    displayName = window.localStorage.displayname || '';
    language = window.localStorage.language;
} else {
    console.log("local storage is not supported");
    userId = generateUniqueId();
}

var Settings = {

    /**
     * Sets the local user display name and saves it to local storage
     *
     * @param newDisplayName the new display name for the local user
     * @returns {string} the display name we just set
     */
    setDisplayName: function (newDisplayName) {
        displayName = newDisplayName;
        window.localStorage.displayname = displayName;
        return displayName;
    },

    /**
     * Returns the currently used by the user
     * @returns {string} currently valid user display name.
     */
    getDisplayName: function () {
        return displayName;
    },

    setEmail: function (newEmail) {
        email = newEmail;
        window.localStorage.email = newEmail;
        return email;
    },

    getSettings: function () {
        return {
            email: email,
            displayName: displayName,
            uid: userId,
            language: language
        };
    },
    setLanguage: function (lang) {
        language = lang;
        window.localStorage.language = lang;
    }
};

module.exports = Settings;